Да, следует избегать увеличения оператора присваивания (+=
) при построении коллекции объектов.
Помимо того факта, что использование оператора +=
обычно требует больше операторов (из-за инициализации массива = @()
) ) и это поощряет хранить целую коллекцию в памяти, а не помещать ее промежуточно в конвейер, неэффективно .
Причина, по которой она неэффективна, заключается в том, что каждый раз, когда вы используете оператор +=
будет выполнять:
$Collection = $Collection + $NewObject
Поскольку массивы неизменны с точки зрения количества элементов, вся коллекция будет воссоздана с каждой итерацией.
Правильный синтаксис PowerShell:
$Collection = 1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
}
Примечание: как и для других командлетов; если есть только один элемент (итерация), на выходе будет скаляр , а не массив, чтобы принудительно преобразовать его в массив, вы можете либо использовать тип [Array]
: [Array]$Collection = 1..$Size | ForEach-Object { ... }
, либо использовать Оператор подвыражения массива @( )
: $Collection = @(1..$Size | ForEach-Object { ... })
Там, где рекомендуется даже не сохранять результаты в переменной ($a = ...
), а затем передавать их в конвейер для экономии памяти, например:
1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
} | ConvertTo-Csv .\Outfile.csv
Примечание: Можно также использовать System.Collections.ArrayList
класс , обычно это почти так же быстро, как конвейер PowerShell, но недостатком является то, что он потребляет намного больше памяти, чем (должным образом) при использовании конвейера PowerShell.
см. Также: Самый быстрый способ получить уникально индексированный элемент из свойства массива
Измерение производительности
Чтобы показать связь с размером коллекции и снижением производительности, вы можете проверить следующий тест Результаты:
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
}
}
По сути то же самое, что:
$Array = ForEach ( ... ) {
$Object1
$Object2
ForEach ( ... ) {
$Object3
Get-Object
}
}
Примечание: если нет итерации, вероятно, нет причин менять ваш скрипт, поскольку, скорее всего, это касается только нескольких дополнений
Назначьте выходные данные итерации (все, что помещено в конвейер) соответствующей переменной. Обычно это на том же уровне, на котором был инициализирован массив (
$Array = @()
). Например:
$Array = ForEach { ...
Примечание 1: Опять же, если вы хотите одиночный Чтобы объект действовал как массив, вы, вероятно, захотите использовать оператор подвыражения Array @( )
, но вы можете также подумать об этом в момент использования массива, например: @($Array).Count
или ForEach ($Item in @($Array))
Примечание 2: Опять же, вам лучше не назначать вывод вообще, но передавать вывод конвейера непосредственно следующему командлету чтобы освободить память: ForEach ( ... ) { ... } | Export-Csv .\File.csv
.
Удалить инициализацию массива
<Variable> = @()
Полный пример см .: Сравнение массивов в Powershell