Я пытаюсь создать довольно большой массив хэш-таблиц, причем большая часть данных либо полностью рандомизирована, либо случайно выбрана из списка.
Вот мой Текущий код
$ArrayData = @()
$ArrayDataRows = 150000
foreach ($i in 1..$ArrayDataRows) {
$thisobject = [PSCustomObject] @{
Number = $i
Place = Get-Random -InputObject NJ, UT, NY, MI, PA, FL, AL, NM, CA, OK, TX, CO, AZ
Color = Get-Random -InputObject red, yellow, blue, purple, green, white, black
Zone = (Get-Random -InputObject $([char[]](65..90)) -Count 10) -join ""
Group = Get-Random -InputObject @(1..20)
}
$ArrayData += $thisobject
}
Однако я замечаю, что это кажется неэффективным. Всего требуется 25 минут, чтобы закончить 150 тыс. строк.
У меня был дополнительный код, не опубликованный здесь, который измерял, сколько времени потребовалось для каждого экземпляра, и оценивал среднее значение от него до его предшественников. Первоначально он давал мне оценку 450 секунд для общего количества и 0,002 в качестве среднего значения для экземпляра для первых 3 тыс. элементов, но позже он просто продолжал медленно ползти до 0,016 или в 8 раз медленнее в среднем.
Как я могу оптимизировать и/или сделать это более эффективным, получая в результате те же данные?
Кроме того, для всех этих объектов InputObject создайте их один раз вне цикла и сохраните в переменных. Может быть незначительным, учитывая небольшие размеры, но все же оптимизация.
самый быстрый способ собрать вещи в коллекцию — это использовать $Collection = foreach () ..., который собирает все элементы в ОЗУ и, наконец, помещает все сразу в $Collection. на самом деле это быстрее, чем методы .Add() типов коллекций ArrayList и Generic.List. [ухмылка]
кроме того, вы НЕ создаете массив хэш-таблиц... вы создаете массив PSCustomObjects. [ухмылка]



[редактировать - вы НЕ создаете массив хэш-таблиц. вы делаете массив из PSCustomObject элементов. [*ухмылка*]]
стандартный массив представляет собой объект исправленный размер. взгляните на $ArrayData.IsFixedSize для подтверждения этого. [ухмылка]
поэтому, когда вы используете += в стандартном массиве, powershell создает НОВЫЙ массив с одним элементом больше, копирует старый в новый и, наконец, добавляет новый элемент. это быстро, когда количество и размер элементов «маленькие», но становится все медленнее [и медленнее, и медленнее] по мере роста количества/размера.
есть два распространенных решения...
.Add()ArrayList [устарело], а Generic.List обычно используют люди. 1-й выводит номер индекса, когда вы добавляете к нему, поэтому, даже если бы он не устарел, я бы не стал его использовать. [ухмылка]$Results = foreach ($Thing in $Collection) {Do-Stuff}, и вывод блока сценария будет храниться в ОЗУ до завершения цикла. то он сразу попадет в коллекцию $Results.2-й самый быстрый.
если у вас нет необходимости менять размер коллекции после ее сборки, то используйте 2-й способ. в противном случае используйте 1-й.
в качестве примера скорости ваш код [с 15 000 элементов] выполняется за 39 секунд в моей системе. использование метода «отправить на выход» занимает 24 секунды.
помните, что замедление будет продолжать ухудшаться по мере увеличения массива. я не хотел ждать 150 тысяч итераций.
вот мой демо-код...
$ArrayDataRows = 15e3
$PlaceList = 'NJ, UT, NY, MI, PA, FL, AL, NM, CA, OK, TX, CO, AZ'.Split(',').Trim()
$ColorList = 'red, yellow, blue, purple, green, white, black'.Split(',').Trim()
$UC_LetterList = [char[]](65..90)
$GroupList = 1..20
(Measure-Command -Expression {
$ArrayData = foreach ($i in 1..$ArrayDataRows) {
[PSCustomObject] @{
Number = $i
Place = Get-Random -InputObject $PlaceList
Color = Get-Random -InputObject $ColorList
Zone = -join (Get-Random -InputObject $UC_LetterList -Count 10)
Group = Get-Random -InputObject $GroupList
}
}
}).TotalMilliseconds
# total ms = 24,390
У вас есть ссылки или советы по использованию Generic.List? Я хотел бы немного разобраться в этом. Я начал использовать ArrayList в своем коде, который требует производительности, и у меня пока не было причин не использовать его. Вывод номера индекса, например, не является проблемой, потому что я всегда бросаю его в пустоту, когда добавляю что-то вроде этого: [void]$ArrayData.Add($i), так что есть другие причины НЕ использовать ArrayList для вас?
@ immobile2 - тип коллекции generic.list - это тот, который рекомендует MS. тип arraylist устарел, а это значит, что он может исчезнуть практически без дополнительного предупреждения. в конце концов он исчезнет. ///// выгода использования типа arraylist заключается в том, что вам не нужно определять тип содержимого — это похоже на массив, в который вы можете запихнуть практически все, что угодно. это тоже один из его недостатков. тип контента generic.list ДОЛЖЕН быть определен — и это может быть полезно, когда полезно принудительное применение типа. вы всегда можете обойти тип, используя [PSObject].[ухмылка]
Полезный ответ Lee_Daily обсуждает важные общие приемы оптимизации построения массивов (коллекций).
Еще одна важная часть головоломки — избегать (несколько) вызовов командлетов внутри цикла, если это возможно.
Замена вызовов Get-Random с использованием [random] (System.Random) обеспечивает наибольшее ускорение (синтаксис PSv5+):
$ArrayDataRows = 150000
$places = 'NJ', 'UT', 'NY', 'MI', 'PA', 'FL', 'AL', 'NM', 'CA', 'OK', 'TX', 'CO', 'AZ'
$colors = 'red', 'yellow', 'blue', 'purple', 'green', 'white', 'black'
$chars = [char[]] (65..90)
$nums = 1..20
# Instantiate a random number generator.
$rndGen = [random]::new()
$ArrayData = foreach ($i in 1..$ArrayDataRows) {
[PSCustomObject] @{
Number = $i
Place = $places[$rndGen.Next(0, $places.Count)]
Color = $colors[$rndGen.Next(0, $colors.Count)]
Zone = -join $(
$charList = [Collections.Generic.List[char]]::new($chars)
foreach ($n in 1..10) { $randIndex = $rndGen.next(0, $charList.count); $charList[$randIndex]; $charList.RemoveAt($randIndex) }
)
Group = $nums[$rndGen.Next(0, $nums.Count)]
}
На моей машине вышеуказанное занимает около 12 секунд, тогда как ваша исходная команда выполнялась около 35 минут (!), что дает ускорение примерно в 175 раз..
Ориентиры:
Ниже приведены примеры времени, которые противопоставляют ваш первоначальный подход, его оптимизированную версию Ли и решение на основе [random] выше; абсолютные числа не важны, но относительная производительность такова, как показано в столбце Factor:
С элементами массива 1000:
Factor Secs (10-run avg.) Command
------ ------------------ -------
1.00 0.100 # with [random]…
12.78 1.273 # with Get-Random - optimized…
13.45 1.340 # with Get-Random - original approach…
Обратите внимание, что при 1000 элементах оптимизация подхода построения массива дает некоторое, но не огромное ускорение, но преимущество тем больше, чем больше элементов.
С элементами массива 10,000:
Factor Secs (10-run avg.) Command
------ ------------------ -------
1.00 1.082 # with [random]…
12.29 13.296 # with Get-Random - optimized…
20.40 22.081 # with Get-Random - original approach…
При наличии 10 000 элементов оптимизация построения массива уже хорошо окупается.
У меня не хватило терпения работать с элементами 150,000, но легко адаптировать следующий код, в котором используется Time-Command функция:
$ArrayDataRows = 1000
$places = 'NJ', 'UT', 'NY', 'MI', 'PA', 'FL', 'AL', 'NM', 'CA', 'OK', 'TX', 'CO', 'AZ'
$colors = 'red', 'yellow', 'blue', 'purple', 'green', 'white', 'black'
$chars = [char[]] (65..90)
$nums = 1..20
Time-Command -Count 10 { # with [random]
# Instantiate a random number generator.
$rndGen = [random]::new()
$ArrayData = foreach ($i in 1..$ArrayDataRows) {
[PSCustomObject] @{
Number = $i
Place = $places[$rndGen.Next(0, $places.Count)]
Color = $colors[$rndGen.Next(0, $colors.Count)]
Zone = -join $(
$charList = [Collections.Generic.List[char]]::new($chars)
foreach ($n in 1..10) { $randIndex = $rndGen.next(0, $charList.count); $charList[$randIndex]; $charList.RemoveAt($randIndex) }
)
Group = $nums[$rndGen.Next(0, $nums.Count)]
}
}
}, { # with Get-Random - optimized
$ArrayData = foreach ($i in 1..$ArrayDataRows) {
[PSCustomObject] @{
Number = $i
Place = Get-Random -InputObject $places
Color = Get-Random -InputObject $colors
Zone = -join (Get-Random -InputObject $chars -Count 10)
Group = Get-Random -InputObject $nums
}
}
} ,{ # with Get-Random - original approach
$ArrayData = @()
foreach ($i in 1..$ArrayDataRows) {
$thisobject = [PSCustomObject] @{
Number = $i
Place = Get-Random -InputObject $places
Color = Get-Random -InputObject $colors
Zone = -join (Get-Random -InputObject $chars -Count 10)
Group = Get-Random -InputObject $nums
}
$ArrayData += $thisobject
}
}
IIRC, ваш
@()делает новую копию каждый раз, когда вы+=. Используйте[System.Collections.ArrayList]и.Add(), и я готов поспорить, что ваша производительность повысится.