gpt4 book ai didi

powershell - 为什么要避免使用递增赋值运算符 (+=) 来创建集合

转载 作者:行者123 更新时间:2023-12-02 22:57:26 25 4
gpt4 key购买 nike

StackOverflow 站点的 += 问答中经常使用递增赋值运算符 ( [PowerShell] ) 来构造集合对象,例如:

$Collection = @()
1..$Size | ForEach-Object {
$Collection += [PSCustomObject]@{Index = $_; Name = "Name$_"}
}

然而,它似乎是一种非常低效的操作。

在 PowerShell 中构建对象集合时应该避免使用递增赋值运算符 ( += ) 吗?

最佳答案

是的,在构建对象集合时应该避免增加赋值运算符( += ),另见: PowerShell scripting performance considerations
除了使用 += 运算符通常需要更多语句(因为数组初始化 = @() )并且它鼓励将整个集合存储在内存中而不是将其中间插入管道这一事实之外, 效率低下
它效率低下的原因是每次使用 += 运算符时,它只会执行以下操作:

$Collection = $Collection + $NewObject
因为数组在元素数量方面是不可变的,整个集合将在每次迭代时 重新创建
正确的 PowerShell 语法是:
$Collection = 1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
}
注意: 与其他 cmdlet 一样;如果只有一项(迭代),则输出将是标量而不是数组,要将其强制为数组,您可以使用 [Array] 类型: [Array]$Collection = 1..$Size | ForEach-Object { ... } 或使用 Array subexpression operator @( ) : $Collection = @(1..$Size | ForEach-Object { ... })建议甚至不要将结果存储在变量( $a = ... )中,而是立即将其传递到管道中以节省内存,例如:
1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
} | ConvertTo-Csv .\Outfile.csv
注意: 也可以考虑使用 System.Collections.ArrayList class,这通常几乎与 PowerShell 管道一样快,但缺点是它比(正确地)使用 PowerShell 管道消耗更多的内存。
另见: Fastest Way to get a uniquely index item from the property of an array
绩效衡量
要显示与集合大小和性能下降的关系,您可以检查以下测试结果:
1..20 | ForEach-Object {
$size = 1000 * $_
$Performance = @{Size = $Size}
$Performance.Pipeline = (Measure-Command {
$Collection = 1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
}
}).Ticks
$Performance.Increase = (Measure-Command {
$Collection = @()
1..$Size | ForEach-Object {
$Collection += [PSCustomObject]@{Index = $_; Name = "Name$_"}
}
}).Ticks
[pscustomobject]$Performance
} | Format-Table *,@{n='Factor'; e={$_.Increase / $_.Pipeline}; f='0.00'} -AutoSize

Size Increase Pipeline Factor
---- -------- -------- ------
1000 1554066 780590 1.99
2000 4673757 1084784 4.31
3000 10419550 1381980 7.54
4000 14475594 1904888 7.60
5000 23334748 2752994 8.48
6000 39117141 4202091 9.31
7000 52893014 3683966 14.36
8000 64109493 6253385 10.25
9000 88694413 4604167 19.26
10000 104747469 5158362 20.31
11000 126997771 6232390 20.38
12000 148529243 6317454 23.51
13000 190501251 6929375 27.49
14000 209396947 9121921 22.96
15000 244751222 8598125 28.47
16000 286846454 8936873 32.10
17000 323833173 9278078 34.90
18000 376521440 12602889 29.88
19000 422228695 16610650 25.42
20000 475496288 11516165 41.29
这意味着使用 20,000 运算符的 += 对象的集合大小比为此使用 PowerShell 管道慢大约 40x
纠正脚本的步骤
显然,有些人难以纠正已经使用递增赋值运算符 ( += ) 的脚本。因此,我创建了一个小说明来这样做:
  • 从相关迭代中移除所有 <variable> += 赋值,只留下对象项。通过不分配对象,对象将简单地放在管道上。
    迭代中是否有多个增加赋值或者是否有嵌入的迭代或函数都没有关系,最终结果将是相同的。
    意思是:
  • ForEach ( ... ) {
    $Array += $Object1
    $Array += $Object2
    ForEach ( ... ) {
    $Array += $Object3
    $Array += Get-Object

    }
    }
    本质上是一样的:
    $Array = ForEach ( ... ) {
    $Object1
    $Object2
    ForEach ( ... ) {
    $Object3
    Get-Object

    }
    }
    注意: 如果没有迭代,则可能没有理由更改您的脚本,因为可能只涉及一些添加
  • 将迭代的输出(放入管道的所有内容)分配给相关变量。这通常与初始化数组的位置 ( $Array = @() ) 处于同一级别。例如:
  • $Array = ForEach { ... 
    注 1: 同样,如果您希望单个对象充当数组,您可能想要使用 Array subexpression operator @( ) 但您也可以考虑在使用数组的那一刻这样做,例如: @($Array).CountForEach ($Item in @($Array)) 注 2: 同样,最好不要分配输出,而是将管道输出直接传递给下一个 cmdlet 以释放内存: ForEach ( ... ) { ... } | Export-Csv .\File.csv
  • 移除数组初始化 <Variable> = @()

  • 有关完整示例,请参阅: Comparing Arrays within Powershell

    关于powershell - 为什么要避免使用递增赋值运算符 (+=) 来创建集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60708578/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com