【问题标题】:How to fill an array efficiently in Powershell如何在 Powershell 中有效地填充数组
【发布时间】:2013-07-26 09:34:13
【问题描述】:

我想使用 Powershell 尽快填充具有相同整数值的动态数组。
Measure-Command 显示我的系统需要 7 秒才能将其填满。
我当前的代码(截断)看起来像:

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

(完整代码可见gist.github.comsuperuser

考虑$length 可以更改。但为了更好地理解,我选择了固定长度。

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

【问题讨论】:

    标签: arrays performance powershell append array-initialization


    【解决方案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 }
    

    【讨论】:

    • 他只是用一些值填充数组?值为“2”
    • 问题说他想用相同的整数值填充数组。他的问题是使用+= 附加到数组非常慢。
    • 嗯!我明白。但为什么?为什么还要找到更好的方法来做一些不需要的事情。无论如何,他也可以使用范围运算符。
    • 我已经将完整的代码附加为 github 链接,只是为了避免讨论 Why。如果您查看链接,您会看到我的 powershell 执行了一个 Excel 命令来查询 CSV。并且该查询的参数TextFileColumnDataTypes 需要一个数组来知道列应该是什么数据类型。 2 代表字符串列,1 代表一般,9 代表跳过整个列等等。所以:长话短说:我需要一个整数值为 2 的大数组。
    • +1 $myArray = 1..16385 | % { 2 } 运行时间为 0.02 秒。比我的 7s 快得多 :)
    【解决方案2】:

    避免在循环中追加到数组。它在每次迭代时将现有数组复制到一个新数组。改为这样做:

    $MyArray = for ($i=1; $i -le $length; $i++) { 2 }
    

    【讨论】:

    • +1 $MyArray = for ($i=1; $i -le 16385; $i++) { 2 } 运行时间为 0.05 秒。比我的 7s 快得多 :)
    【解决方案3】:

    使用 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 秒内运行
    【解决方案4】:

    你可以重复数组,就像你可以对字符串做的那样:

    $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
    

    通过对Measure-Command 运行每个变体 50 次,每个变体具有相同的 $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 简洁、清晰、有建设性、全面且可重复! (嗯,五分之四……)
    【解决方案5】:

    如果你真的需要它,那么就使用 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值Tuple的目的是什么?这意味着您必须访问Item1 属性才能取回该值,另外您正在创建一个Object 来包装每个Int32,这在较大的列表中将是很多垃圾。这并没有那么糟糕,因为使用过时的、非泛型的 ArrayList 类意味着它将在 Object 中装箱每个 Int32,无论如何。重写为 $myArray = New-Object 'Collections.Generic.List[Int32]'; foreach($i in 1..$length) { $myArray.add(2) } 我得到了 40% 的加速和更少的字符/复杂度。
    • 另外,每个property of a Tuple 都是只读的,所以如果你想更改一个列表值(这必然会发生,因为......有什么好处是重复值的列表始终保持一样吗?)您唯一的选择是创建一个新的Tuple 来替换它。
    • 即使元组部分对于上述挑战来说并不是真正需要的,但值得记住的是,用每个对象的多个列/项填充大型只读数组。按不同的列对非常大的数组/查找表进行排序非常方便。没有元组,也不需要对任何东西进行排序。
    猜你喜欢
    • 2014-09-21
    • 2021-06-24
    • 1970-01-01
    • 1970-01-01
    • 2018-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-27
    相关资源
    最近更新 更多