powershell - 构建 HashMap 的 HashMap

标签 powershell hashmap hashtable

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

所以让我们看看,下面的代码工作得很好并且达到了它的目的。代码的结果是 HashMap 的 HashMap ,我需要它作为另一项工作的查找表。

背景:

  • $ccDb 是一个由大约 200k 项组成的数组,属性为 companyCd、costCenterNbr、costCenterShortNm、costCenterLongDescr
  • 每个属性都必须修剪(请不要要求我修剪我的 Db,遗憾的是我不能)。
  • costCenterNbr 包含在 companyCd 中,这意味着每个 companyCd 可以有多个 costCenterNbr
  • companyCd 可以包含 X 数量的 costCenterNbr
  • costCenterNbr 具有唯一值,与 companyCd 相同。
  • costCenterShortNmcostCenterLongDescrcostCenterNbr 相关

问题:

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

问题:

您是否认为可以改进此代码以实现更快/更高效的执行?

$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# 可能会更高效,但事实就是如此。

编辑:添加测量值以供引用。

measure1

measure2

编辑:

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

enter image description here

最佳答案

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

这是您最大的水槽:

    $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 的维护者):
    • 安装模块 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
        }
    }
}
  • 等待代码完成
  • 查看输出:
  • 
        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
    

    关于powershell - 构建 HashMap 的 HashMap ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66715054/

    相关文章:

    android - 使用 getLastNonConfigurationInstance 恢复 Activity 状态会生成未经检查的强制转换警告

    java - Custom HashMap代码问题的实现

    java - 请分享一些关于java中的rehash方法的见解?

    linux - 在 VMware VCenter 连接自动化中运行 powershell/powercli 脚本

    powershell - 什么是 ShellId?为什么我需要它?

    azure - 如何使用 PowerShell 将 Api 权限添加到 Azure 应用程序注册

    data-structures - 用于有效返回哈希表(映射、字典)的前 K 个条目的数据结构

    c++ - 实时应用程序中最坏情况时间执行的最快 C++ 映射?

    c++ - 哈希表是否允许重复值?

    c# - 如何在 PowerShell 中使用 Windows API AuditEnumerateCategories 函数?