【问题标题】:Building a Hashmap of Hashmaps构建 Hashmap 的 Hashmap
【发布时间】:2021-06-17 06:26:23
【问题描述】:

我不经常提问(大多数时候问题可以通过一些研究来解决,对吧?)但我只是想听听您的意见,因为可能有更好的(更有效的方法)。

所以让我们看看,下面的代码运行良好,它达到了它的目的。代码的结果是哈希图的哈希图,我需要它作为另一项工作的查找表。

背景:

  • $ccDb 是一个由大约 200k 项组成的数组,属性为 companyCd, costCenterNbr, costCenterShortNm, costCenterLongDescr
  • 必须修剪每个属性(请不要让我修剪我的 Db,很遗憾我不能)。
  • costCenterNbr包含在companyCd上,意思是每个companyCd可以有多个costCenterNbr
  • companyCd 可以包含 X 个 costCenterNbr
  • costCenterNbr 具有唯一值,companyCd 也是如此。
  • costCenterShortNmcostCenterLongDescrcostCenterNbr 相关

问题:

必须在每次运行我的脚本时构建此映射,因为信息是从 SQL 表中获取的(它一直在变化)。构建这张地图大约需要 15 分钟(在相当不错的服务器上,2CPU 12Cores)。

问题:

您是否看到可以改进此代码以更快/更有效地执行的方法?

$ccMap=@{}

foreach($line in $ccDb)
{
    $companyCd=$line.companyCd.trim()
    $costCenterNbr=$line.costCenterNbr.trim()
    $costCenterShortNm=$line.CostCenterShortNm.trim()
    $costCenterLongDescr=$line.CostCenterLongDescr.trim()
    
    $coceMap=@{
        $costCenterNbr=@{
            shortDesc=$costCenterShortNm
            longDesc=$costCenterLongDescr
        }
    }
    
    if($ccMap.ContainsKey($companyCd))
    {
        $ccMap[$companyCd]+=$coceMap
    }
    else
    {
        $ccMap.Add($companyCd,$coceMap)
    }
}

对于冗长的解释,我深表歉意,但我觉得最好预先提供最多的信息。很感谢任何形式的帮助。此外,我知道 PowerShell 对于我正在做的事情来说是一种非常糟糕的语言,而 C# 可能会更高效,但它就是这样。

编辑:添加测量值以供参考。

编辑:

非常感谢@Mathias R. Jessen,这是他的代码的测量结果。优秀的代码。

【问题讨论】:

  • 您可以使用 -AsHashTable 开关查看 Group-Object。
  • 听起来你在做经典的数据管理。经典的数据库管理系统 (DBMS) 非常适合这类事情。您的数据显然来自 SQL,所以我假设它来自 DBMS。为什么不在 SQL 中进行数据转换和缩减,然后将缩减后的数据传递给 Powershell?
  • 是的,已经做到了,组对象要慢得多。
  • $ccDb 中有很多重复的companyCd 值吗? :-)
  • 您只是在使用 SQL 模块吗?您应该也可以让数据库为您修剪它

标签: powershell hashmap hashtable


【解决方案1】:

我有两个建议:

你根本不需要if\else\.add()语句,powershell会根据需要添加密钥。这应该会占用大部分时间,因为您没有在整个表中搜索每个条目:

$ccMap[$companyCd]+=$coceMap

如果您只使用一次值,则不需要在上面设置变量。只需使用您的$line

$coceMap=@{
    $line.costCenterNbr.trim()=@{
        shortDesc = $line.CostCenterShortNm.trim()
        longDesc  = $line.CostCenterLongDescr.trim()
    }
}

【讨论】:

  • "powershell 将根据需要添加密钥。"
  • @MathiasR.Jessen 在这里是正确的,因为它运行了超过 20 分钟,所以无法判断它是否有效,所以我决定停止它。
【解决方案2】:

不要在紧密循环中使用+=

这是你最大的水槽:

    $ccMap[$companyCd] += $coceMap

当您使用+(或+=)将一个哈希表添加到另一个哈希表时,PowerShell 会创建一个全新的哈希表

# Create two different hashtables
$A = @{ Key1 = 'Value1' }
$B = @{ Key2 = 'Value2' }

# Let's save a second reference to the first table
$remember = $A

# Now let's use += to merge the two:
$A += $B

运行这个,你会发现 $B$remember 没有改变,但是 $A 有两个键 - 因此必须是一个新的。

要避免这种性能损失,请完全跳过$coceMap 的构造,并颠倒顺序(如果不存在则先构造哈希表,然后分配):

$ccMap=@{}

foreach($line in $ccDb)
{
    $companyCd=$line.companyCd.trim()
    $costCenterNbr=$line.costCenterNbr.trim()
    $costCenterShortNm=$line.CostCenterShortNm.trim()
    $costCenterLongDescr=$line.CostCenterLongDescr.trim()

    # Create new hashtable if none exist, otherwise retrieve the existing one
    if($ccMap.ContainsKey($companyCd))
    {
        $coceMap = $ccMap[$companyCd]
    }
    else
    {
        $coceMap = $ccMap[$companyCd] = @{}
    }
    
    $coceMap[$costCenterNbr] = @{
        shortDesc=$costCenterShortNm
        longDesc=$costCenterLongDescr
    }
}

基准测试+=

下面是一个简化示例,说明与 10000 项具有 50 个不同键的差异:

$data = @(
    1..10000 |Select-Object @{Name='Company';Expression={Get-Random -Maximum 50}},@{Name='CostCenter';Expression={Get-Random}}
)

@(
    Measure-Command {
        $map = @{}

        foreach($line in $data){
            $entry = @{
                $line.CostCenter = @{
                    Value = 123
                }
            }

            if($map.ContainsKey($line.Company)){
                $map[$line.Company] += $entry
            }
            else {
                $map[$line.Company] = $entry
            }
        }
    }

    Measure-Command {
        $map = @{}

        foreach($line in $data){
            if($map.ContainsKey($line.Company)){
                $entry = $map[$line.Company]
            }
            else {
                $entry = $map[$line.Company] = @{}
            }

            $entry[$line.CostCenter] = @{
                Value = 123
            }
        }
    }
) |select TotalMilliseconds

在我的笔记本电脑上显示:

TotalMilliseconds
-----------------
         306.4218
          47.8164

一般如何识别这样的时间槽?

有多种方法可以分析 PowerShell 的运行时行为,但这是我个人的首选:

  1. 安装PSProfiler免责声明:我是PSProfiler的维护者):
    • Install-Module PSProfiler -Scope CurrentUser
  2. 使用 Measure-Script 的方式与使用 Measure-Command 的方式相同:
Measure-Script {
    $map = @{}

    foreach($line in $data){
        $entry = @{
            $line.CostCenter = @{
                Value = 123
            }
        }

        if($map.ContainsKey($line.Company)){
            $map[$line.Company] += $entry
        }
        else {
            $map[$line.Company] = $entry
        }
    }
}
  1. 等待代码完成
  2. 查看输出:

    Anonymous ScriptBlock


      Count  Line       Time Taken Statement
      -----  ----       ---------- ---------
          0     1    00:00.0000000 {
          1     2    00:00.0000187     $map = @{}
          0     3    00:00.0000000
          0     4    00:00.0000000     foreach($line in $data){
      10000     5    00:00.0635585         $entry = @{
          0     6    00:00.0000000             $line.CostCenter = @{
          0     7    00:00.0000000                 Value = 123
          0     8    00:00.0000000             }
          0     9    00:00.0000000         }
          0    10    00:00.0000000
          0    11    00:00.0000000         if($map.ContainsKey($line.Company)){
       9950    12    00:00.3965227             $map[$line.Company] += $entry
          0    13    00:00.0000000         }
          0    14    00:00.0000000         else {
         50    15    00:00.0002810             $map[$line.Company] = $entry
          0    16    00:00.0000000         }
          0    17    00:00.0000000     }
          0    18    00:00.0000000 }

观察到第 12 行占用了最多的总执行时间 - 明显多于其他任何时间:

       9950    12    00:00.3965227             $map[$line.Company] += $entry

【讨论】:

  • 谢谢!让我检查一下,我会尽快更新。
  • 我在这里可能错了,但这不会产生任何输出$coceMap = $ccMap.Add($companyCd, @{})。我收到以下异常:System.Management.Automation.RuntimeException: Cannot index into a null array.
  • 好吧,你疯了。我正在尝试掌握您的代码,但这太聪明了。看看我的编辑。非常感谢您的帮助。
  • @santisq 我添加了一个更简单的示例以及我如何进行测量以识别它,也许它可以帮助您推理它?
  • 很好的解释,肯定会安装你的模块。我理解你的大部分代码,除了$entry = $map[$line.Company] = @{} 这是我以前从未见过的。需要详细研究它才能更好地理解,但我已经筋疲力尽了 rn:P 再次感谢!
猜你喜欢
  • 2011-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多