Powershell 2和.NET:针对极大的哈希表进行优化?

时间:2011-09-23 00:53:49

标签: powershell hashtable powershell-v2.0

我正在涉足Powershell并且对.NET来说是全新的。

我正在运行一个以空哈希表开头的PS脚本。哈希表将增长到至少15,000到20,000个条目。哈希表的键将是字符串形式的电子邮件地址,值将是布尔值。 (我只需要跟踪我是否看过电子邮件地址。)

到目前为止,我一直在增加哈希表一个条目。我检查以确保键值对不存在(PS会在这种情况下出错),然后我添加该对。

以下是我们谈论的代码部分:

...
    if ($ALL_AD_CONTACTS[$emailString] -ne $true) {
      $ALL_AD_CONTACTS += @{$emailString = $true}
    }
...

我想知道从PowerShell或.NET的角度来看,如果你知道它会提前很大,例如15,000到20,000个条目或更多,那么是否可以做任何事情来优化这个哈希表的性能。 / p>

谢谢!

3 个答案:

答案 0 :(得分:5)

我使用Measure-Command使用一组20 000 random words执行了一些基本测试。

单个结果显示如下,但总的来说,通过首先使用单个条目分配新哈希表来添加到一个哈希表是非常低效的:)尽管选项2到5之间存在一些小的效率提升,但总的来说他们都表现得差不多。

如果我选择的话,我可能会倾向于选项5的简单性(每个字符串只有一个Add个调用),但我测试的所有替代方案似乎都可行。

$chars = [char[]]('a'[0]..'z'[0])
$words = 1..20KB | foreach {
  $count = Get-Random -Minimum 15 -Maximum 35
  -join (Get-Random $chars -Count $count)
}

# 1) Original, adding to hashtable with "+=".
#     TotalSeconds: ~800
Measure-Command {
  $h = @{}
  $words | foreach { if( $h[$_] -ne $true ) { $h += @{ $_ = $true } } }
}

# 2) Using sharding among sixteen hashtables.
#     TotalSeconds: ~3
Measure-Command {
  [hashtable[]]$hs = 1..16 | foreach { @{} }
  $words | foreach {
    $h = $hs[$_.GetHashCode() % 16]
    if( -not $h.ContainsKey( $_ ) ) { $h.Add( $_, $null ) }
  }
}

# 3) Using ContainsKey and Add on a single hashtable.
#     TotalSeconds: ~3
Measure-Command {
  $h = @{}
  $words | foreach { if( -not $h.ContainsKey( $_ ) ) { $h.Add( $_, $null ) } }
}

# 4) Using ContainsKey and Add on a hashtable constructed with capacity.
#     TotalSeconds: ~3
Measure-Command {
  $h = New-Object Collections.Hashtable( 21KB )
  $words | foreach { if( -not $h.ContainsKey( $_ ) ) { $h.Add( $_, $null ) } }
}

# 5) Using HashSet<string> and Add.
#     TotalSeconds: ~3
Measure-Command {
  $h = New-Object Collections.Generic.HashSet[string]
  $words | foreach { $null = $h.Add( $_ ) }
}

答案 1 :(得分:3)

所以几周后,我无法想出完美的解决方案。 Google的一位朋友建议将哈希分成几个较小的哈希值。他建议每次我去查找一个密钥时,我会发现几个未命中,直到找到正确的“桶”,但他说当读取惩罚算法运行时,读取惩罚不会像写入惩罚那样糟糕。将条目插入(已经很大的)哈希表。

我接受了这个想法并且更进了一步。我将哈希分成16个较小的桶。当将电子邮件地址作为密钥插入数据结构时,我实际上首先计算电子邮件地址本身的哈希值,然后执行mod 16操作以获得0到15之间的一致值。然后我将该计算值用作“斗“号。

因此,我没有使用一个巨型哈希,而是实际拥有一个16元素的数组,其元素是电子邮件地址的哈希表。

使用拆分散列表存储桶构建20,000多个电子邮件地址的“主列表”的内存中表示所需的总速度现在大约快了1,000%。 (快10倍)。

访问散列中的所有数据没有明显的速度延迟。这是迄今为止我能够提出的最佳解决方案。它有点难看,但性能提升本身就说明了一切。

答案 2 :(得分:2)

你将花费大量的CPU时间在Hashtable中重新分配内部'数组'。你试过.NET constructor for Hashtable that takes a capacity吗?

$t = New-Object Hashtable 20000
...
if (!($t.ContainsKey($emailString))) { 
    $t.Add($emailString, $emailString) 
}

我的版本使用相同的$ emailString作为密钥和&amp;值,没有像对象一样对[对象]进行$ true的拳击。在PowerShell“if”条件中,非null字符串将计算为$ true,因此您检查的其他代码不应更改。在性能敏感的.NET代码中使用'+ = @ {...}'将是一个很大的禁忌。您可能只是使用“@ {}”语法为每封电子邮件分配一个新的Hashtable,这可能会浪费大量时间。

将非常大的集合分解为(相对较小的)较小集合的方法称为“分片”。你应该使用Hashtable构造函数,即使你的分片数是16,它也会占用一个容量。

另外,@ Larold是对的,如果你没有查找电子邮件地址,那么使用'New-Object ArrayList 20000'来创建预先分配的列表。

此外,收藏品也会逐渐增长(每个“增长”因子为1.5或2)。这样做的结果是你应该能够减少按照一个人工顺序预先分配的数量,如果集合每次“数据加载”调整一次或两次,你可能不会注意到。我敢打赌,这是需要时间的前10-20代“增长”。