【问题标题】:Powershell - Fastest way to create a very large array of hashtablesPowershell - 创建一个非常大的哈希表数组的最快方法
【发布时间】:2019-08-23 05:39:19
【问题描述】:

我正在尝试创建一个相当大的哈希表数组,其中大部分数据要么完全随机,要么从列表中随机挑选。

这是我的当前代码

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

但我注意到,它似乎效率不高。完成 150k 行总共需要 25 分钟。

我有一些额外的代码没有在这里发布,这些代码测量了每个实例所花费的时间,并估计了从它到其前身的平均值。最初,它会给我一个估计 450 秒的总数和 0.002 作为前 3k 项的每个实例的平均值,但后来它只是缓慢地爬升到平均速度的 0.016 或 8 倍。

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

【问题讨论】:

  • IIRC,您的 @() 每次您 += 时都会制作一个新副本。使用[System.Collections.ArrayList].Add() 我敢打赌你的性能会提高。
  • 此外,对于所有这些 InputObjects,在循环外创建它们一次并存储在变量中。考虑到小尺寸,可以忽略不计,但仍然是一种优化。
  • 将东西放入集合的最快方法是使用 $Collection = foreach () ... 将所有项目收集到 RAM 中,最后将所有项目一次性放入$Collection。它实际上比ArrayListGeneric.List 集合类型的.Add() 方法快。 [咧嘴]
  • 另外,您不是在制作一个哈希表数组......您正在制作一个 PSCustomObjects 数组。 [咧嘴]

标签: arrays performance powershell optimization hashtable


【解决方案1】:

[编辑 - 你不是在制作一个哈希表数组。您正在制作一组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

【讨论】:

  • 您对使用Generic.List 有任何参考或提示吗?我想调查一下。我已经开始在我的一些需要性能的代码中使用ArrayList,而且我还没有理由不使用它——还没有。例如,输出索引号不是问题,因为当我添加类似这样的内容时,我总是将其转换为空白:[void]$ArrayData.Add($i),所以还有什么其他理由不为您使用ArrayList
  • @immobile2 - generic.list 集合类型是 MS 推荐的集合类型。 arraylist 类型已被弃用 - 这意味着它可能会在很少或没有额外警告的情况下消失。 它最终会消失。 ///// 使用arraylist 类型的好处是你不需要定义内容类型——它就像一个数组,你几乎可以把任何东西塞进去。 这也是它的缺点之一。 必须定义 generic.list 内容类型 - 这在类型强制有用时很有用。您始终可以使用 [PSObject].[grin] 回避类型
【解决方案2】:

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

难题的另一个重要部分是尽可能避免在循环中调用(多个)cmdlet

使用[random] (System.Random) 替换Get-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 
    }
  }

【讨论】:

    猜你喜欢
    • 2010-09-29
    • 2011-07-15
    • 1970-01-01
    • 2011-08-29
    • 2018-01-26
    • 2021-02-15
    • 1970-01-01
    • 2018-08-18
    • 2011-05-06
    相关资源
    最近更新 更多