当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 构建 PowerShell 以提高速度

作者:精品下载站 日期:2024-12-14 13:01:40 浏览:14 分类:玩电脑

构建 PowerShell 以提高速度


在每个脚本编写者的旅程中,脚本永远不够快。为了解决这个问题,我们将介绍一些如何使 Powershell 速度达到最高速度的方法。

我们不会在本文中介绍多线程 PowerShell 脚本。您可以在《PowerShell 多线程:深入探讨》一文中阅读所有相关内容。在本文中,我们将重点关注测量性能、加速循环、使用数组提高速度等等!

测量 PowerShell 速度

至关重要的是,在进行性能测试时,您可以可靠地测试和重新测试代码并在测试之间获得相似的性能数据。 PowerShell 包含 Measure-Command cmdlet,它允许您运行脚本块并测量运行时间。

在大多数情况下,您只需在 Expression 参数脚本块内使用代码运行 Measure-Command 即可获得所需内容。但是,如果您的代码可能已经可以快速运行,并且您在几十毫秒或更短的时间内测量差异,则可能需要像下面的代码一样循环您的代码。

在此示例中,您希望 Get-ChildItem 命令能够快速完成,但如果将代码放入循环中并多次运行相同的操作,您可以更轻松地判断毫秒差异。

foreach ($i in 1..1000) {
	Get-ChildItem
}

如果您的代码将被重复使用(例如在包含数百万个项目的循环中),您可能会精确地测量性能。在这种情况下,每个循环 10 毫秒的差异可能意味着数小时的执行时间。

作为这种差异的示例,您可以使用下面的脚本块创建一个文件并获取该文件的文件哈希 3 次。由于获取文件哈希的性能取决于许多变量,因此很容易显示多次运行相同命令时可能看到的微小差异。

New-Item .\test.txt
Measure-Command {Get-FileHash .\test.txt}
Measure-Command {Get-FileHash .\test.txt}
Measure-Command {Get-FileHash .\test.txt}

您会期望这段代码每次运行时大约在同一时间完成,因为它做同样的事情。以下是运行测试的结果。

[玩转系统] 构建 PowerShell 以提高速度

[玩转系统] 构建 PowerShell 以提高速度

[玩转系统] 构建 PowerShell 以提高速度

这种速度差异可能是由于多种原因造成的,包括运行代码的设备上的负载、结果缓存、设备正在使用的存储上的负载、内存或分页文件的使用情况,或者设备上任何其他正在运行的任务设备。

在这种情况下,可以通过在循环中运行代码来更好地了解其平均执行情况而不是选择单个实例,从而受益匪浅。

Foreach-Object 与 Foreach 循环速度

虽然循环经常用于处理许多项目,但当出现性能问题时,它们可能成为确凿的证据。

以此脚本块为例。它循环 10,000 次,并输出前一个数字的乘积和输入的当前数字。该示例可以在任何机器上轻松重复,并且可以通过更改命令循环次数来快速扩展。

1..10000 | ForEach-Object {
	if ($Last) { $Last * $_ } 
	$Last = $_
}

这是上面代码的输出:

[玩转系统] 构建 PowerShell 以提高速度

虽然在许多情况下这已经足够快了,但如果相同的代码运行数十万或数百万次,则可能会导致严重的延迟。您可以改为使用 foreach 语句,如下所示:

foreach ($Num in 1..10000) { 
	if ($Last) { $Last * $Num } 
	$Last = $Num
}

此代码提供相同的输出,但由于使用了 foreach 语句而不是 ForEach-Object

[玩转系统] 构建 PowerShell 以提高速度

您的性能差异结果将根据循环中的具体代码而有所不同,但您应该亲自尝试一下。

为了更快地执行,有时您可以完全省略循环,而只依赖管道来执行整组命令。下面是一些代码,它将循环遍历目录中的所有文件,并使用 foreach 语句获取每个文件的文件哈希值。

运行 Get-ChildItem 命令时,它会输出有关文件的信息,包括文件的路径。这可以通过 Get-FileHash 命令直接通过管道使用。

foreach ($File in Get-ChildItem) { 
	Get-FileHash $File
}

[玩转系统] 构建 PowerShell 以提高速度

编写此代码的更好方法是在没有循环的单个管道中,如下所示:

Get-ChildItem | Get-FileHash

这些代码集会输出相同的内容,但后者显示出巨大的性能提升。

[玩转系统] 构建 PowerShell 以提高速度

这个包含 Get-ChildItemGet-FileHash 的示例表明,您应该在可以的情况下从代码中删除循环,并利用管道替换它们。

在 PowerShell 中管理循环以提高速度将极大地提高性能。

数组与列表:PowerShell Speed++

从某些代码中收集结果以供以后使用是很常见的。如下所示的内容在互联网上传播的许多代码中经常使用。它运行我们在前面部分中使用的相同代码来输出当前数字和先前数字的乘积。在这种情况下,代码还将输出添加到$Array

$Array = @()
foreach ($Num in 1..10000) {
	if ($Last) { $Array += $Last * $Num } 
	$Last = $Num
}

在此示例中,输出将保存到 $Array 中以供稍后使用或输出所有结果。

[玩转系统] 构建 PowerShell 以提高速度

您应该使用 ArrayList 对象,而不是使用数组。使用的语法略有不同。首先,您需要使用 New-Object System.Collections.ArrayList 专门定义 ArrayList。您还需要使用 .Add() 方法,而不是像使用数组那样使用 += 方法。以下是使用 ArrayList 而不是普通数组的更新代码块:

$ArrayList = New-Object System.Collections.ArrayList
foreach ($Num in 1..10000) { 
	if ($Last) { $ArrayList.Add($Last * $Num) }
	$Last = $Num
}

以下是上述代码的结果。

[玩转系统] 构建 PowerShell 以提高速度

标准数组无法扩展其中对象的数量。通过使用+=,PowerShell 创建一个更大的新数组,并将旧数据和新数据复制到其中。只需执行几次此操作是添加到数组的快速方法,但在这种情况下,如果发生数千次,则会极大地影响性能。

随着您使用更大量的数据,这种性能差异会继续扩大。对于 100,000 个项目而不是 10,000 个项目,执行时间差异几乎为 5 分钟与半秒。不过,这种性能差异也存在相反的情况,如果您只处理少数项目,则差异很小。

[玩转系统] 构建 PowerShell 以提高速度

在上面的两个示例中,您将命令的输出保存到变量中。使用变量来保存输出很常见,但在大多数情况下不是必需的。通过跳过将输出保存到变量,您可以看到相对于保存到 ArrayList 的另一个小好处。

[玩转系统] 构建 PowerShell 以提高速度

通过跳过将输出保存到变量的步骤,输出将在计算时发送,而不是输出。

请注意如何创建列表和数组。如果这样做,您将大大提高 PowerShell 脚本的速度。

向左过滤

PowerShell 中最常见的命令之一是 Where-Object,它用于过滤另一个命令的输出。虽然它非常强大,但只应在需要的地方使用它。确保尽可能“向左过滤”,以提高 PowerShell 脚本的速度。

虽然上面列出的其他选项需要权衡,您会因速度而失去一些便利,但通常建议使用左侧过滤来提高性能。无论您在 PowerShell 中做什么,您都应该尽快过滤掉大量数据。

例如,下面的代码块用于获取文件夹中的项目,然后过滤到以数字 1 开头的项目。

Get-ChildItem | Where-Object {$_.Name -like '1*'}

上面的代码只用了 422 毫秒就完成了,但它仍然可以做得更好。您可以使用以下命令在命令内进行过滤,而不是在命令运行后进行过滤:

Get-ChildItem -Filter 1*

您将收到相同的输出,但它将减少通过管道处理的数据量,因此它会在 30 毫秒内完成。这种巨大的差异是由于过滤器的执行方式造成的。在第一个示例中,每个目录文件都被传递给 Where-Object 命令以确定是否应该继续。在第二个示例中,Get-ChildItem 在沿管道发送的任何结果之前过滤结果。

从 PowerShell 3 开始,您可以在没有脚本块的情况下使用 Where-Object。根据您使用的脚本块,它可能会稍微提高执行速度。在下面的代码中,平均只节省了 10ms。回到最初的示例,非脚本块格式如下所示:

Get-ChildItem | Where-Object Name -like '1*'

使用非脚本块语法获得的微小收益可能不值得失去与 PowerShell 2 的向后兼容性,因为有多少系统仍在运行它。不过,在 PowerShell 4 中,还有另一个选项,即使用 .Where() 方法。这只能用于集合,因此语法与其他选项有很大不同。下面是使用 .Where() 方法的同一示例:

(Get-ChildItem).Where({$_.Name -like '1*'})

[玩转系统] 构建 PowerShell 以提高速度

此选项比前面提到的其他选项完成得快得多。这种性能差异表明,如果您使用的 cmdlet 不支持筛选器参数,并且您不需要支持旧版本的 PowerShell,则使用 .Where() 方法可能是最佳选择速度的最佳选择。

话虽如此,.Where() 方法可能有点难以阅读,因此您可以选择更标准的 Where-Object {} 格式以获得更好的可读性,具体取决于它对您的情况有多大影响。

错误先检查

无论您正在访问的系统是否可用或现在是否可用,您可能都需要执行某些操作。如果您提前检查错误,您将提高 PowerShell 脚本的速度,仅仅是因为效率的提高。

例如,如果文件存在,您希望将其删除;如果文件不存在,则不执行任何操作。您可以使用类似下面的代码。它将循环遍历当前目录中标记为 1 到 10000 的所有文件,并删除它们(如果存在)。

foreach ($Item in 1..10000) {Remove-Item $Item}

上述代码的缺点是,如果文件不存在,您将收到错误。您可能会想通过使用 -ErrorAction SilentlyContinue 来消除此错误,这样就不再抛出该错误,如下所示:

foreach ($Item in 1..10000) {Remove-Item $Item -ErrorAction SilentlyContinue}

使用 -ErrorAction 参数来静默错误会遇到很多问题,例如,如果存在除文件不存在之外的问题,您将永远不会知道,而无需进行额外的故障排除。除此之外,虽然您没有看到错误,但它仍然在后台抛出,这会造成一些性能影响。运行上面的代码会产生以下输出。

[玩转系统] 构建 PowerShell 以提高速度

您可以先检查文件是否存在,然后仅在存在时删除它,而不是盲目地尝试删除它存在或不存在的文件:

foreach ($Item in 1..10000) {
	if (Test-Path $Item) { Remove-Item $Item }
}

起初,您可能认为检查文件是否存在比尝试删除它需要更长的时间。正如您在这里所看到的,它几乎用了一半的时间就完成了,并且您还可以获得额外的好处,即能够使用正确的错误检测和处理可能发生的其他错误。

[玩转系统] 构建 PowerShell 以提高速度

使用 if 语句先检查文件是否存在并不总能提高性能。如果您的代码每百万次尝试中仅尝试删除一次不存在的文件,那么不每次都进行检查会更快。尽管如此,这确实允许您稍后轻松添加错误处理。

总结

通过我们介绍的所有 PowerShell 速度调整,您可以改进代码以更有效地运行它。在某些情况下,您可能不会注意到由于使用小数据集而导致的运行时差异。尽管如此,如果您将代码提供给其他人,他们可能会从更高效的代码中获得显着的改进。

由于无法确定您的代码将来将如何使用,因此提高您的代码效率永远不会有什么坏处。因此,虽然您可能无法看到更改代码以提高性能的差异,但其他人可能会欣赏将来的工作。

其中一些建议确实有一些缺点,例如兼容性较低或可读性较差,但会产生更快的代码。因此,某些代码只有在需要更好的性能时才需要优化,但这对于大多数优化技术来说都是一样的。

补充阅读

要更深入地了解 Array 和 ArrayList 之间的差异,您可以查看这篇文章。如需多线程处理 PowerShell 代码的帮助,您可以查看此处。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯