如何在Powershell中高效填充数组

8
我希望能够使用Powershell尽快地填充一个动态数组,并且使用相同的整数值。
Measure-Command 显示在我的系统上,需要7秒来填充它。
我当前的代码(被截断)如下:
$myArray = @()
$length = 16385
for ($i=1;$i -le $length; $i++) {$myArray += 2}  

(完整代码可在gist.github.comsuperuser上查看)

考虑到$length可能会发生变化,但为了更好的理解,我选择了一个固定长度。

问: 如何加快这个 Powershell 代码的运行速度?

5个回答

22
您可以重复数组,就像您可以重复字符串一样:
$myArray = ,2 * $length

这意味着»获取只有一个元素2的数组,并重复它$length次,生成一个新数组。«。

请注意,您无法真正使用此方法创建多维数组,因为以下原因:

$some2darray = ,(,2 * 1000) * 1000

如果只是创建了1000个对内部数组的引用,这些引用对于操作来说就毫无用处。在这种情况下,您可以使用混合策略。我曾经使用过

$some2darray = 1..1000 | ForEach-Object { ,(,2 * 1000) }

过去,但下面的性能测量结果表明

$some2darray = foreach ($i in 1..1000) { ,(,2 * 1000) }

会是一个更快的方式。


一些性能测量:

Command                                                  Average Time (ms)
-------                                                  -----------------
$a = ,2 * $length                                                 0,135902 # my own
[int[]]$a = [System.Linq.Enumerable]::Repeat(2, $length)           7,15362 # JPBlanc
$a = foreach ($i in 1..$length) { 2 }                             14,54417
[int[]]$a = -split "2 " * $length                                24,867394
$a = for ($i = 0; $i -lt $length; $i++) { 2 }                    45,771122 # Ansgar
$a = 1..$length | %{ 2 }                                         431,70304 # JPBlanc
$a = @(); for ($i = 0; $i -lt $length; $i++) { $a += 2 }       10425,79214 # original code

通过每个变量运行50次Measure-Command,每个变量都使用相同的值$length,并对结果取平均值。

实际上,位置3和4有点出乎意料。显然最好使用foreach循环一个范围,而不是使用常规的for循环。


生成上图的代码:

$length = 16384

$tests = '$a = ,2 * $length',
         '[int[]]$a = [System.Linq.Enumerable]::Repeat(2, $length)',
         '$a = for ($i = 0; $i -lt $length; $i++) { 2 }',
         '$a = foreach ($i in 1..$length) { 2 }',
         '$a = 1..$length | %{ 2 }',
         '$a = @(); for ($i = 0; $i -lt $length; $i++) { $a += 2 }',
         '[int[]]$a = -split "2 " * $length'

$tests | ForEach-Object {
    $cmd = $_
    $timings = 1..50 | ForEach-Object {
        Remove-Variable i,a -ErrorAction Ignore
        [GC]::Collect()
        Measure-Command { Invoke-Expression $cmd }
    }
    [pscustomobject]@{
        Command = $cmd
        'Average Time (ms)' = ($timings | Measure-Object -Average TotalMilliseconds).Average
    }
} | Sort-Object Ave* | Format-Table -AutoSize -Wrap

1
+1 简明、清晰、有建设性、全面且可复现!(好吧,五个 C 中有四个...) - Michael Sorens

6
避免在循环中向数组追加元素。这会导致每次迭代都将现有数组复制到一个新数组中。您可以使用以下方法代替:
$MyArray = for ($i=1; $i -le $length; $i++) { 2 }

+1 $MyArray = for ($i=1; $i -le 16385; $i++) { 2 } 运行时间为0.05秒,比我的7秒快得多 :) - nixda

5

使用 PowerShell 3.0,您可以使用以下功能(需要 .NET Framework 3.5 或更高版本):

[int[]]$MyArray = ([System.Linq.Enumerable]::Repeat(2, 65000))

使用PowerShell 2.0

$AnArray = 1..65000 | % {2}

+1 [int[]]$myArray = ([System.Linq.Enumerable]::Repeat(2, 16385)) 在0.03秒内运行。 - nixda

1

不清楚你尝试做什么。我尝试查看了你的代码。但是,$myArray +=2 表示你只是将2添加为元素。例如,这是我的测试代码输出:

$myArray = @()
$length = 4
for ($i=1;$i -le $length; $i++) {
    Write-Host $myArray
    $myArray += 2
}

2
2 2
2 2 2

为什么需要多次将2添加到数组元素中?
如果您只想填充相同的值,请尝试以下方法:
$myArray = 1..$length | % { 2 }

1
他只是用一些值填充数组吗?这个值是'2'。 - JPBlanc
问题是他想用相同的整数值填充数组。他的问题是使用 += 追加到数组中非常慢。 - Ansgar Wiechers
嗯!我明白了。但是为什么?为什么要寻找更好的方法来做一些不需要的事情呢?不管怎样,他也可以使用range运算符。 - ravikanth
我已经将完整的代码附加为Github链接,只是为了避免关于“为什么”的讨论。如果您查看链接,您会发现我的PowerShell执行了一个Excel命令来查询CSV。而该查询的参数TextFileColumnDataTypes需要一个数组来知道列应该是什么数据类型。2代表字符串列,1代表一般列,9代表跳过整个列等等。所以:长话短说:我需要一个包含整数值2的大数组。 - nixda
1
$myArray = 1..16385 | % { 2 } 运行时间为0.02秒,比我的7秒快得多 :) - nixda
当我测试1..$length时,它的速度明显比for ($i=1; $i -lt $length; $i++)慢。很可能是因为它在传递到ForEach-Object循环之前正在构建列表。 - Ansgar Wiechers

-1
如果你需要非常快的速度,那么使用ArrayLists和Tuples:
$myArray = New-Object 'Collections.ArrayList'
$myArray = foreach($i in 1..$length) {
    [tuple]::create(2)
}

如果需要稍后排序,则可以使用以下代码(一般会慢一些):

$myArray = New-Object 'Collections.ArrayList'
foreach($i in 1..$length) {
    $myArray.add(
        [tuple]::create(2)
    )
}

对我来说,两个版本都在20毫秒的范围内 ;-)


虽然这比问题中的代码更快,但使用1值元组的目的是什么?这意味着您必须访问Item1属性才能获取值,而且您正在创建一个对象来包装每个Int32,在较大的列表上会产生大量垃圾。这并不会对使用过时的非泛型ArrayList类造成太大影响,因为它会将每个Int32装箱到一个Object中。重写为$myArray = New-Object 'Collections.Generic.List[Int32]'; foreach($i in 1..$length) { $myArray.add(2) },我获得了40%的速度提升,并且字符/复杂度更少。 - Lance U. Matthews
此外,每个Tuple属性都是只读的,因此如果您想更改绑定到列表值的内容(这是不可避免的,因为...重复值列表总是保持不变有什么用?),您唯一的选择是创建一个新的Tuple来替换它。 - Lance U. Matthews
即使元组部分对于上述挑战实际上并不需要,但值得记住的是,可以使用它来填充具有多个列/项目的大型只读数组。非常方便地按不同的列对非常大的数组/查找表进行排序。没有元组和任何需要排序的需求。 - Carsten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接