Powershell-创建大量哈希表的最快方法

时间:2019-04-02 01:09:24

标签: arrays performance powershell optimization hashtable

我正在尝试创建一个相当大的哈希表数组,其中许多数据是完全随机的或从列表中随机选择的。

这是我的当前代码

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

我注意到的是,它似乎效率不高。完成15万行总共需要25分钟。

我这里未发布一些其他代码,该代码测量了每个实例花费了多长时间,并估计了每个实例到其先前实例的平均值。最初,它可以让我估计总共450秒,而对于前3k个项目,平均每个实例为0.002,但后来它一直缓慢地爬升到平均速度的0.016或8倍。

如何在获得相同数据的同时优化和/或提高效率?

2 个答案:

答案 0 :(得分:2)

[编辑-您未在创建哈希表数组。您正在制作PSCustomObject个项目的数组。 [*咧嘴*]]

标准数组是固定大小对象。请查看$ArrayData.IsFixedSize进行确认。 [咧嘴]

因此,当您在标准阵列上使用+=时,powershell会制作一个新的,单项较大的阵列,将旧的复制到新的阵列中,最后添加新的阵列。当项目数量和尺寸“很小”时,它很快,但是随着数量/尺寸的增加,它变得越来越慢[越来越慢,越来越慢]。

有两种常见的解决方案...

  • 使用具有.Add()方法的集合类型
    ArrayList(不推荐使用)和Generic.List是人们通常使用的工具。当添加到第一个时,它会输出一个索引号,因此即使它不被弃用,我也不会使用它。 [咧嘴]
  • 使用输出流
    您可以使用$Results = foreach ($Thing in $Collection) {Do-Stuff},脚本块的输出将保留在RAM中,直到循环完成。然后将其全部填充到$Results集合中。

第二个是最快的。

如果构建后无需更改集合的大小,请使用第二种方法。否则使用第一个。

作为速度的示例,您的代码(包含15,000个项目)在我的系统上运行39秒。使用“发送到输出”技术需要24秒。

请记住,随着数组变大,速度将继续恶化。我不愿意等待15万次迭代。

这是我的演示代码...

$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 :(得分:1)

Lee_Daily's helpful answer讨论了有关构建数组(集合)的重要常规优化技术

这个难题的另一个重要方面是,如果可能,避免在循环内执行(多个)cmdlet调用

使用Get-RandomSystem.Random)替换[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的加速因子


基准

以下是与您的原始方法,Lee的优化版本以及上面基于[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 function的代码:

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