Powershell - самый быстрый способ создать очень большой массив хеш-таблиц - PullRequest
1 голос
/ 02 апреля 2019

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

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

    $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 
     }

Однако я замечаю, что это неэффективно. Для завершения 150 000 строк требуется всего 25 минут.

У меня здесь не было дополнительного кода, который измерял, сколько времени потребовалось на каждый экземпляр, и оценивал среднее значение от него до его предшественников. Первоначально, это дало бы мне оценку на 450 секунд для общего количества и 0,002 как Среднее для каждого экземпляра первых 3k предметов, но позже оно просто продолжало медленно ползти до 0,016 или в 8 раз медленнее в среднем.

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

Ответы [ 2 ]

2 голосов
/ 02 апреля 2019

[edit - вы НЕ создаете массив хеш-таблиц.вы делаете массив из PSCustomObject предметов.[* grin *]]

стандартный массив - это объект фиксированного размера .посмотрите на $ArrayData.IsFixedSize для подтверждения этого.[ ухмылка ]

поэтому, когда вы используете += для стандартного массива, powershell создает НОВЫЙ массив размером на один элемент больше, копирует старый в новый инаконец добавляет новый элемент.это быстро, когда количество и размер элемента «маленькие», но становится медленнее [и медленнее, и медленнее] по мере роста количества / размера.

существует два распространенных решения ...

  • использует тип коллекции, который имеет .Add() метод
    ArrayList [устарел] и Generic.Listте люди обычно используют.1-й выводит индексный номер при добавлении к нему, поэтому, даже если он не устарел, я бы не использовал его.[ ухмылка ]
  • использовать выходной поток
    , который вы можете использовать $Results = foreach ($Thing in $Collection) {Do-Stuff}, и вывод блока скрипта будет храниться в ОЗУ до завершения цикла.тогда он будет сразу вставлен в коллекцию $Results.

2-й самый быстрый.

если вам не нужно изменять размер коллекции после ее создания, используйте второй способ.в противном случае используйте 1-й.

В качестве примера скорости ваш код [с 15 000 элементов] запускается в моей системе за 39 секунд.использование техники «отправить на вывод» занимает 24 секунды.

помните, что замедление будет ухудшаться по мере увеличения массива.я не хотел ждать 150 000 итераций.

вот мой демонстрационный код ...

$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
1 голос
/ 03 апреля 2019

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


Тесты :

Ниже приведены примеры времени, которые контрастируют с вашим первоначальным подходом, оптимизированной версией Ли и решением на основе [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 
    }
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...