为了补充先前存在的有用答案,并提供有关何时使用哪种方法以及性能比较的指南。
对于较小的输入集合(数组),您可能不会注意到其中的区别,尤其是在命令行上,有时能够轻松键入命令更为重要。
这是一种易于键入的替代方法,但是这是最慢的方法。它使用称为操作语句的简化ForEach-Object
语法(再次是PSv3 +):例如,以下PSv3 +解决方案很容易附加到现有命令中:
$objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name }
为了完整起见:本文中讨论的鲜为人知的PSv4 + .ForEach()
阵列方法是另一种替代方法:
# By property name (string):
$objects.ForEach('Name')
# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
这种方法与成员枚举类似,但有相同的取舍,只是不应用管道逻辑。它或多或少地更慢,但仍明显比管道更快。
为了通过名称(字符串参数)提取单个属性值,此解决方案与成员枚举相当(尽管后者在语法上更简单)。
的脚本块变体,允许任意变换 ; 它是基于管道的ForEach-Object
cmdlet(%
)的一次更快的全内存替代方法。
比较各种方法的性能
这是基于输入的对象集合的各种方法的采样时间,这些10,000
对象是 10次运行的平均值。绝对数字并不重要,并且会因许多因素而有所不同,但这绝对会让您有相对的性能(计时来自单核Windows 10 VM:
重要
相对性能取决于输入对象是常规.NET类型的实例(例如,由输出Get-ChildItem
)还是[pscustomobject]
实例(例如,由输出Convert-FromCsv
)。
原因是[pscustomobject]
属性由PowerShell动态管理,并且与(静态定义的)常规.NET类型的常规属性相比,它可以更快地访问它们。两种情况都在下面介绍。
这些测试使用内存已满的集合作为输入,从而专注于纯属性提取性能。使用流cmdlet /函数调用作为输入时,性能差异通常不会那么明显,因为在该调用中花费的时间可能占花费的大部分时间。
为简便起见,别名%
用于ForEach-Object
cmdlet。
一般结论,适用于常规.NET类型和[pscustomobject]
输入:
到目前为止,成员枚举($collection.Name
)和foreach ($obj in $collection)
解决方案是最快的,比最快的基于管道的解决方案快十倍甚至更多。
令人惊讶的是,% Name
它的性能要差得多% { $_.Name }
-请参阅此GitHub问题。
在这里,PowerShell Core始终优于Windows Powershell。
计时与普通.NET类型:
- PowerShell Core v7.0.0-preview.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.005
1.06 foreach($o in $objects) { $o.Name } 0.005
6.25 $objects.ForEach('Name') 0.028
10.22 $objects.ForEach({ $_.Name }) 0.046
17.52 $objects | % { $_.Name } 0.079
30.97 $objects | Select-Object -ExpandProperty Name 0.140
32.76 $objects | % Name 0.148
- Windows PowerShell v5.1.18362.145
Comparing property-value extraction methods with 10000 input objects, averaged over 10 runs...
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.012
1.32 foreach($o in $objects) { $o.Name } 0.015
9.07 $objects.ForEach({ $_.Name }) 0.105
10.30 $objects.ForEach('Name') 0.119
12.70 $objects | % { $_.Name } 0.147
27.04 $objects | % Name 0.312
29.70 $objects | Select-Object -ExpandProperty Name 0.343
结论:
- 在PowerShell Core中,其
.ForEach('Name')
性能明显优于.ForEach({ $_.Name })
。奇怪的是,在Windows PowerShell中,后者速度更快,尽管速度虽然很小。
[pscustomobject]
实例的时间:
- PowerShell Core v7.0.0-preview.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.006
1.11 foreach($o in $objects) { $o.Name } 0.007
1.52 $objects.ForEach('Name') 0.009
6.11 $objects.ForEach({ $_.Name }) 0.038
9.47 $objects | Select-Object -ExpandProperty Name 0.058
10.29 $objects | % { $_.Name } 0.063
29.77 $objects | % Name 0.184
- Windows PowerShell v5.1.18362.145
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.008
1.14 foreach($o in $objects) { $o.Name } 0.009
1.76 $objects.ForEach('Name') 0.015
10.36 $objects | Select-Object -ExpandProperty Name 0.085
11.18 $objects.ForEach({ $_.Name }) 0.092
16.79 $objects | % { $_.Name } 0.138
61.14 $objects | % Name 0.503
结论:
请注意,[pscustomobject]
输入.ForEach('Name')
远远优于基于脚本块的变体.ForEach({ $_.Name })
。
同样,在Windows PowerShell中,[pscustomobject]
输入Select-Object -ExpandProperty Name
实际上使基于管道的速度更快.ForEach({ $_.Name })
,但实际上在PowerShell Core中仍然慢了约50%。
简而言之:奇怪的例外是% Name
,使用[pscustomobject]
基于字符串的引用属性的方法要优于基于脚本块的方法。
测试的源代码:
注意:
$count = 1e4 # max. input object count == 10,000
$runs = 10 # number of runs to average
# Note: Using [pscustomobject] instances rather than instances of
# regular .NET types changes the performance characteristics.
# Set this to $true to test with [pscustomobject] instances below.
$useCustomObjectInput = $false
# Create sample input objects.
if ($useCustomObjectInput) {
# Use [pscustomobject] instances.
$objects = 1..$count | % { [pscustomobject] @{ Name = "$foobar_$_"; Other1 = 1; Other2 = 2; Other3 = 3; Other4 = 4 } }
} else {
# Use instances of a regular .NET type.
# Note: The actual count of files and folders in your home dir. tree
# may be less than $count
$objects = Get-ChildItem -Recurse $HOME | Select-Object -First $count
}
Write-Host "Comparing property-value extraction methods with $($objects.Count) input objects, averaged over $runs runs..."
# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Name },
{ $objects | % Name },
{ $objects | % { $_.Name } },
{ $objects.ForEach('Name') },
{ $objects.ForEach({ $_.Name }) },
{ $objects.Name },
{ foreach($o in $objects) { $o.Name } }
# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Factor, Command, Secs*
$results = @($objects | %{ $_.Name })
。尽管我认为Scott的答案通常更好,但有时在命令行中键入更为方便。