Powershell — самый быстрый способ создать очень большой массив хэш-таблиц

Я пытаюсь создать довольно большой массив хэш-таблиц, причем большая часть данных либо полностью рандомизирована, либо случайно выбрана из списка.

Вот мой Текущий код

    $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 раз медленнее в среднем.

Как я могу оптимизировать и/или сделать это более эффективным, получая в результате те же данные?

IIRC, ваш @() делает новую копию каждый раз, когда вы +=. Используйте [System.Collections.ArrayList] и .Add(), и я готов поспорить, что ваша производительность повысится.

Kory Gill 02.04.2019 03:22

Кроме того, для всех этих объектов InputObject создайте их один раз вне цикла и сохраните в переменных. Может быть незначительным, учитывая небольшие размеры, но все же оптимизация.

Kory Gill 02.04.2019 03:25

самый быстрый способ собрать вещи в коллекцию — это использовать $Collection = foreach () ..., который собирает все элементы в ОЗУ и, наконец, помещает все сразу в $Collection. на самом деле это быстрее, чем методы .Add() типов коллекций ArrayList и Generic.List. [ухмылка]

Lee_Dailey 02.04.2019 03:44

кроме того, вы НЕ создаете массив хэш-таблиц... вы создаете массив PSCustomObjects. [ухмылка]

Lee_Dailey 02.04.2019 03:58
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
4
1 067
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

[редактировать - вы НЕ создаете массив хэш-таблиц. вы делаете массив из 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 13.09.2021 22:14

@ immobile2 - тип коллекции generic.list - это тот, который рекомендует MS. тип arraylist устарел, а это значит, что он может исчезнуть практически без дополнительного предупреждения. в конце концов он исчезнет. ///// выгода использования типа arraylist заключается в том, что вам не нужно определять тип содержимого — это похоже на массив, в который вы можете запихнуть практически все, что угодно. это тоже один из его недостатков. тип контента generic.list ДОЛЖЕН быть определен — и это может быть полезно, когда полезно принудительное применение типа. вы всегда можете обойти тип, используя [PSObject].[ухмылка]

Lee_Dailey 15.09.2021 15:09

Полезный ответ 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 
    }
  }

Другие вопросы по теме