【问题标题】:Dictionary with tuple key slower than nested dictionary. Why?带有元组键的字典比嵌套字典慢。为什么?
【发布时间】:2018-02-26 10:20:27
【问题描述】:

我已经测试了使用 (int, int, string) 元组作为键与使用嵌套 Dictionary: Dictionary>> 的字典中检索、更新和删除值的速度。

我的测试显示元组字典要慢很多(58% 用于检索,69% 用于更新,200% 用于删除)。我原本没想到。嵌套字典需要做更多的查找,为什么元组字典要慢很多?

我的测试代码:

    public static object TupleDic_RemoveValue(object[] param)
    {
        var dic = param[0] as Dictionary<(int did, int eid, string name), string>;
        var keysToRetrieve = param[2] as List<(int did, int eid, string name)>;

        foreach (var key in keysToRetrieve)
        {
            dic.Remove(key);
        }

        return dic;

    }


    public static object NestedDic_RemoveValue(object[] param)
    {
        var dic = param[1] as Dictionary<int, Dictionary<int, Dictionary<string, string>>>;
        var keysToRetrieve = param[2] as List<(int did, int eid, string name)>;


        foreach (var key in keysToRetrieve)
        {
            if (dic.TryGetValue(key.did, out var elementMap) && elementMap.TryGetValue(key.eid, out var propertyMap))
                propertyMap.Remove(key.name);
        }

        return dic;

    }

关于测试的额外信息: 该词典共包含 10 000 个条目。键正在递增:([0-100],[0-100],"Property[0-100]")。 在一次测试中,检索了 100 个键(其中 10% 不存在于字典中),更新了 100 个值(其中 10% 是新的)或删除了 100 个键(其中 10% 不在字典中开始和)。检索、更新和删除是 3 个单独的测试。每个测试执行 1000 次。我比较了平均执行时间和中位数执行时间。

【问题讨论】:

  • 如何计算元组的HashCode() - 与为intintstring 计算3 个单独的HashCodes() 相比,它的成本是否更高?你用什么样本量来测量,你用什么时间计算的?也许将显示的代码放大为真正的Minimal, Complete, and Verifiable example。拥有 3 个 dicts 可以更快地将数据切割成更小的分区进行搜索,然后拥有一个巨大的字典 - 因此后者在设计上可能会更快。最好将元组与非匿名类进行比较。这比您所做的更公平。
  • 添加了有关已执行测试的更多信息。我们目前在代码中使用嵌套字典。我们正在考虑尽可能使用元组字典,但速度不会慢很多,因为这些字典操作在某些情况下是我们应用程序的瓶颈。

标签: c# .net performance dictionary


【解决方案1】:

Dictionary 中的查找依赖于两件事。第一个是项目的哈希码,用于将项目分成桶。两个 不同 键可以具有 相同 哈希码,因此一旦找到潜在匹配项,就会针对每个项目(使用该哈希码)调用 Equals 直到完全匹配被发现。

ValueTuple 的哈希码实现(对于 arity-2+ *)将元组中每个项目的Equality Comparer.Default&lt;T&gt;.GetHashCode 的结果传递给内部方法ValueTuple.CombineHashCodes,该方法又调用System.Numerics.Hashing.HashHelpers.Combine。元组中的项目越多,对Combine 方法的嵌套调用就越多。将此与普通的intGetHashCode 进行比较,后者只是直接返回值。

您的后一个示例会更快,这对我来说很有意义。正如 cmets 中所指出的,您还将必要的数据切割成更小的分区。每次查找都必须调用GetHashCode,并在找到潜在匹配时调用Equals。在我看来,在第一种情况下哈希冲突的可能性更高,这意味着对Equals 的更多调用(在这种情况下只是对元组中的每个项目对EqualityComparer&lt;T&gt;.Default.Equals 的调用)。

最后归结为分析(更确切地说,是正确分析--发布模式、jitting 调用、足够的迭代等)以及您的特定用例。

如果性能真的在您的用例中很重要(例如,在紧密循环中查找),也许最好使用您自己的类型和哈希码/等于实现而不是ValueTuples。但同样,它归结为分析。

* 请注意,1 元元组有一种特殊情况。

HashHelpers.Combine

ValueTuple

Int32.GetHashCode

【讨论】:

    猜你喜欢
    • 2011-11-01
    • 1970-01-01
    • 2019-01-12
    • 2022-11-22
    • 2021-12-29
    • 2022-11-21
    • 2020-03-13
    • 1970-01-01
    • 2019-03-17
    相关资源
    最近更新 更多