【问题标题】:Simple Dictionary Lookup is Slow in .Net Compared to Flat Array与平面数组相比,.Net 中的简单字典查找速度较慢
【发布时间】:2010-10-25 06:16:27
【问题描述】:

我发现与平面数组访问相比,字典查找可能非常慢。知道为什么吗?我正在使用 Ants Profiler 进行性能测试。这是一个重现问题的示例函数:

    private static void NodeDisplace()
    {
        var nodeDisplacement = new Dictionary<double, double[]>();

        var times = new List<double>();
        for (int i = 0; i < 6000; i++)
        {
            times.Add(i * 0.02);
        }
        foreach (var time in times)
        {
            nodeDisplacement.Add(time, new double[6]);
        }

        var five = 5;
        var six = 6;
        int modes = 10;
        var arrayList = new double[times.Count*6];
        for (int i = 0; i < modes; i++)
        {
            int k=0;
            foreach (var time in times)
            {
                for (int j = 0; j < 6; j++)
                {

                    var simpelCompute = five * six;  // 0.027 sec
                    nodeDisplacement[time][j] = simpelCompute;  //0.403 sec
                    arrayList[6*k+j] = simpelCompute;  //0.0278 sec
                }

                k++;
            }
        }
    }

注意到平面数组访问和字典访问之间的相对大小了吗?考虑到数组索引操作 (6*k+j) 后,平面数组比字典访问 (0.403/0.0278) 快大约 20 倍。

听起来很奇怪,但字典查找占用了我大部分时间,我必须对其进行优化。

【问题讨论】:

  • 但是索引访问通常比哈希查找快...你说的怪异是什么意思?
  • @Mitch,他用 ANTS Profiler 测量
  • @Nick,但相差 20 倍?
  • @Ngu:当然。还有很多工作要做。数组访问对于 CPU 来说是绝对的礼物——它几乎不需要做任何事情
  • 你不是在比较苹果和橙子吗?您用于字典和数组访问的索引不一样。当然,使用整数索引的数组访问比使用任意类型 (double) 的字典访问要快。 并且您的字典访问执行附加数组访问,而其他代码中只有一个数组访问。考虑到数组访问基本上没有成本,20 倍听起来是对的。

标签: c# performance


【解决方案1】:

是的,我并不感到惊讶。字典的重点是它们用于查找任意键。考虑单个数组取消引用会发生什么:

  • 检查边界
  • 索引乘以元素大小
  • 为指针添加索引

非常非常快。现在进行字典查找(非常粗略;取决于实现):

  • 可能检查密钥是否为空
  • 获取密钥的哈希码
  • 为该哈希码找到正确的槽(可能是“mod prime”操作)
  • 可能取消引用数组元素以查找该槽的信息
  • 比较哈希码
  • 如果哈希码匹配,则比较是否相等(并可能继续下一个哈希码匹配)

如果你有“键”,可以很容易地用作数组索引(例如连续整数,或者可以很容易映射到连续整数的东西) 那么这将非常非常快。这不是哈希表的主要用例。它们适用于 无法 以这种方式轻松映射的情况 - 例如通过字符串或 任意 double 值查找(而不是双精度均匀分布,因此可以很容易地映射到整数)。

我会说您的标题具有误导性 - 并不是字典查找速度很慢,而是当数组是一种更合适的方法时,它们的速度快得离谱。

【讨论】:

  • am 使用间隔均匀的double(参见上面的代码),但它仍然很慢。
  • @Ngu:这就是我的观点——您使用的范围很容易映射到整数,因此请改用数组。为作业使用最合适的数据结构——在本例中是一个数组。
【解决方案2】:

另外Jon's answer我想补充一点,你的内循环并没有做太多,通常你在内循环中至少做一些工作,然后字典的相对性能损失会稍微低一些。

如果您查看 Reflector 中 Double.GetHashCode() 的代码,您会发现它正在执行 4 行代码(假设您的 double 不是 0),这不仅仅是您的内部循环的主体。 Dictionary&lt;TKey, TValue&gt;.Insert()(由 set indexer 调用)代码更多,几乎占满一屏。

与平面数组相比,Dictionary 的优势在于,当您的键不密集(就像您的情况一样)并且读取和写入类似于数组的 ~O(1) 时,您不会浪费太多内存(但具有更高的常数)。

作为旁注,您可以使用多维数组而不是 6*k+j 技巧。
以这种方式声明它

var arrayList = new double[times.Count, 6];

并以这种方式使用它

arrayList[k ,j] = simpelCompute;

不会更快,但更容易阅读。

【讨论】:

    猜你喜欢
    • 2018-07-01
    • 2015-08-14
    • 1970-01-01
    • 2020-11-07
    • 2010-12-16
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    • 2021-04-19
    相关资源
    最近更新 更多