为什么要避免使用递增赋值运算符 (+=) 创建集合
Why should I avoid using the increase assignment operator (+=) to create a collection
递增赋值运算符(+=
)常用于[PowerShell]
Whosebug 站点的问答中构造集合对象,例如:
$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 and
性能测量
要显示与集合大小和性能下降之间的关系,您可以检查以下测试结果:
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
个对象使用+=
运算符大约是40x
比为此使用 PowerShell 管道慢。
更正脚本的步骤
显然,有些人在纠正已经使用递增赋值运算符 (+=
) 的脚本时遇到了困难。因此,我创建了一个小指令来执行此操作:
- 从相关迭代中删除所有
<variable> +=
赋值,仅保留对象项。通过不分配对象,对象将简单地放在管道上。
不管迭代中是否有多次递增赋值,或者是否有嵌入迭代或函数,最终结果都是一样的。
意思是:
ForEach ( ... ) {
$Array += $Object1
$Array += $Object2
ForEach ( ... ) {
$Array += $Object3
$Array += Get-Object
}
}
本质上等同于:
ForEach ( ... ) {
$Object1
$Object2
ForEach ( ... ) {
$Object3
Get-Object
}
}
注意: 如果没有迭代,可能没有理由只可能更改您的脚本关注一些补充
- 将迭代的输出(所有放在管道上的东西)分配给相关的变量。这通常与数组初始化 (
$Array = @()
) 的级别相同。例如:
$Array = ForEach ( ... ) { ...
注1:同样,如果你想让单个对象充当数组,你可能想要使用 Array subexpression operator @( )
但您也可以考虑在使用数组时执行此操作,例如:@($Array).Count
或 ForEach ($Item in @($Array))
注 2: 同样,您最好 不要 分配输出全部。相反,将管道输出直接传递给下一个 cmdlet 以释放内存:... | ForEach-Object {...} | Export-Csv .\File.csv
.
- 去除数组初始化
<Variable> = @()
有关完整示例,请参阅:
请注意,这同样适用于使用 +=
构建 strings,
参见:Is there a string concatenation shortcut in PowerShell?
递增赋值运算符(+=
)常用于[PowerShell]
Whosebug 站点的问答中构造集合对象,例如:
$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 and
性能测量
要显示与集合大小和性能下降之间的关系,您可以检查以下测试结果:
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
个对象使用+=
运算符大约是40x
比为此使用 PowerShell 管道慢。
更正脚本的步骤
显然,有些人在纠正已经使用递增赋值运算符 (+=
) 的脚本时遇到了困难。因此,我创建了一个小指令来执行此操作:
- 从相关迭代中删除所有
<variable> +=
赋值,仅保留对象项。通过不分配对象,对象将简单地放在管道上。
不管迭代中是否有多次递增赋值,或者是否有嵌入迭代或函数,最终结果都是一样的。
意思是:
ForEach ( ... ) {
$Array += $Object1
$Array += $Object2
ForEach ( ... ) {
$Array += $Object3
$Array += Get-Object
}
}
本质上等同于:
ForEach ( ... ) {
$Object1
$Object2
ForEach ( ... ) {
$Object3
Get-Object
}
}
注意: 如果没有迭代,可能没有理由只可能更改您的脚本关注一些补充
- 将迭代的输出(所有放在管道上的东西)分配给相关的变量。这通常与数组初始化 (
$Array = @()
) 的级别相同。例如:
$Array = ForEach ( ... ) { ...
注1:同样,如果你想让单个对象充当数组,你可能想要使用 Array subexpression operator @( )
但您也可以考虑在使用数组时执行此操作,例如:@($Array).Count
或 ForEach ($Item in @($Array))
注 2: 同样,您最好 不要 分配输出全部。相反,将管道输出直接传递给下一个 cmdlet 以释放内存:... | ForEach-Object {...} | Export-Csv .\File.csv
.
- 去除数组初始化
<Variable> = @()
有关完整示例,请参阅:
请注意,这同样适用于使用 +=
构建 strings,
参见:Is there a string concatenation shortcut in PowerShell?