更快地替换Dictionary <tkey,tvalue =“”> </tkey,>

时间:2009-12-08 20:01:06

标签: c# .net performance generics dictionary

我需要快速替换System.Collections.Generic.Dictionary<TKey, TValue>。我的应用程序应该非常快。因此,替换应该支持:

  • 泛型
  • 添加
  • 获取
  • 包含

......就是这样。我不需要LINQ或任何支持。它应该快速

一个简单的代码,如:

Stopwatch stopWatch = Stopwatch.StartNew();

Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("fieldName", "fieldValue");
dictionary.Add("Title", "fieldVaaaaaaaaaaaaaaaaalue");

Console.WriteLine(stopWatch.Elapsed);

...打印00:00:00.0001274,对我来说很多时间,因为我的应用程序正在做很多其他的事情,其中​​一些来自我必须使用的旧慢库不依赖我。

关于如何实施更快的任何想法?

谢谢。

10 个答案:

答案 0 :(得分:62)

你有可能看到JIT编译。在我的盒子上,我看到了:

00:00:00.0000360
00:00:00.0000060

当我在同一个进程中快速连续运行两次时 - 而不是在调试器中。 (确保你没有在调试器中运行它,否则这是一个毫无意义的测试。)

现在,衡量 微小的任何时间通常是一个坏主意。你需要迭代数百万次才能更好地了解它需要多长时间。

您是否有充分的理由相信实际上会降低您的代码速度 - 或者您是基于原始时机进行的?

我怀疑你会发现任何明显比Dictionary<TKey, TValue>更快的东西,我会惊讶地发现它是瓶颈。

编辑:我刚刚对Dictionary<TKey, TValue>添加一百万个元素进行基准测试,其中所有键都是现有对象(数组中的字符串),重用相同的值(因为它不相关)并指定一个容量百万在建 - 在我两岁的笔记本电脑上用了大约0.15秒。

这个真的可能会成为你的瓶颈,因为你已经说过你在应用程序的其他地方使用了一些“旧的慢速库”吗?请记住,其他库越慢,改进的集合类的影响就越小。如果字典更改仅占整个应用程序时间的1%,那么即使我们可以提供瞬时字典,您也只能将应用程序加速1%。

与以往一样,获取一个分析器 - 它可以让您更好地了解您的时间。

答案 1 :(得分:31)

我同意Jon Skeet的假设,即这很可能是JIT编译。

话虽这么说,我想在这里添加一些其他信息:

与使用Dictionary<T,U>相关的大多数速度问题与Dictionary的实现无关。 Dictionary<T,U>非常快,开箱即用。打败它会很困难。

与Dictionary实例相关的速度问题几乎总是实际上是哈希代码实现问题。如果您在使用Dictionary<MyCustomClass,MyValue>时遇到速度问题,请重新访问您在MyCustomClass上定义的GetHashCode()实施。如果您使用自定义结构作为密钥,则这一点更为重要。

为了从词典中获得良好的表现,GetHashCode()应为:

  1. 快速
  2. 能够提供产生很少冲突的哈希码。在可能的情况下,唯一实例应生成唯一的哈希值。
  3. 如果你做得对,我认为你会对默认的Dictionary实现感到非常满意。

答案 2 :(得分:7)

不要忘记,您也在该代码中对Dictionary构造函数进行计时。我做了一个测试,将测量中的调用移到了构造函数中,然后循环了10次。这是我的测试代码:

for (int i = 0; i < 10; i++)
{
    Dictionary<string, string> test = new Dictionary<string, string>();

    System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();

    test.Add("fieldName", "fieldValue");
    test.Add("Title", "fieldavlkajlkdjflkjalkjslkdjfiajwelkrjelrkjavoijl");

    Console.WriteLine(watch.Elapsed);
}

Console.ReadKey();

以下是结果:

00:00:00.0000607
00:00:00.0000025
00:00:00.0000015
00:00:00.0000015
00:00:00.0000016
00:00:00.0000017
00:00:00.0000016
00:00:00.0000016
00:00:00.0000016
00:00:00.0000015

我不确定你能获得多快的速度......

<强>更新

看起来这也反映了Jon Skeets的结果... JIT。

答案 3 :(得分:5)

如果你真的需要更好的性能,你将不得不放弃一些重要的东西 - 比如泛型,动态内存分配等。所有这些功能都会牺牲一些性能。

如果可能的话,我会避免使用Contains并查看TryGetValue等。

答案 4 :(得分:2)

以最大性能使用INTS作为键:

对于那些从谷歌来到这里的人来说,如果你想从词典中挤出最后一点性能,那就用Ints作为键。这是比较Int与String Keys的基准: https://jacksondunstan.com/articles/2527

文章的作者甚至提到,如果你有这样的需要,将字符串转换为int是值得的。

另外,请注意在PHP等其他语言中也会出现同样的行为。 Php关联数组 - 实际上是字典,如果你在PHP7中以升序使用Ints ,它们会非常优于字符串键。

答案 5 :(得分:1)

你可能没有比Dictionary更快找到任何东西。我会用字典。然后,当你发现你没有达到你的性能目标,并且一个分析器表明从词典添加/删除是你的瓶颈时,你可以考虑用更有针对性的类替换。

请注意,如果不使用LINQ等功能,则不会导致任何性能损失。

答案 6 :(得分:1)

你可以使用List并定义一个枚举,例如,fieldName = 0,Title = 1并使用每个属性的唯一索引作为列表中的查找索引吗?这将是最快的解决方案,但由于你被绑定到枚举,因此灵活性最低。

答案 7 :(得分:1)

您打算在字典中添加多少项?虽然Dictionary / Hashtable通常是最快的,但取决于你正在做什么,可能比Hashtable(字典中的底层结构)更快(也称为更适合)。根据用法,如果与某种跳过列表甚至自平衡树或尝试结合使用,SortedList可能会更快。特别是如果您希望返回一系列值而不是单个值。

Hashtable非常适合:

  1. 您知道在表格开始之前您打算存储多少项目。动态调整大小会非常痛苦!
  2. 你有一个很好的哈希算法和均匀分布,.NET就是
  3. 有一个很好的机制来解决冲突,.NET确实
  4. 您正在寻找单一值
  5. 您可以保证所有值都是唯一的
  6. 例如,如果您正在进行压缩,则RB-Tree优于Hashtable。

    来源:http://en.wikipedia.org/wiki/Hashtable#Dynamic_resizing

答案 8 :(得分:1)

字典允许指定的IEqualityComparer比较器。 对于字符串或其他类型的通用比较可能不是最佳性能。 如果使用默认的==比较器,则有一点ILSpy会告诉您,如果您的实现遇到性能问题,则可以注入自己的IEqualityComparer比较器。 最后,字典会比较您提供的键的哈希码与条目列表中的现有哈希码。

因此,如果您有特定的需求字典,也许可以将其专门用于FastDictionary类,从而以更有效的方式获取hascode,

在您的实现中,将是:

var dictionary = new Dictionary<string, string>(StringComparer.Ordinal); 

答案 9 :(得分:0)

除了上述所有内容之外,还请注意以下几点:

  1. 您可以通过将括号(在方括号中)的初始大小传递给构造函数,从而{Pre}在Dictionary对象中初始化哈希存储区数组。例如。 301?
Dictionary<string, string> dictionary = new Dictionary<string, string>( 301 );
  1. 取决于您需要更快的addget,您可能还发现将重点放在针对Add/Remove或仅Retrieve的优化上很重要。这意味着,有时需要更快地定位和检索,而不是添加或删除它们。在您的情况下,您在示例dictionary.Add方法中提到了该问题,但还是有人问这个问题,以便在 general 中更快地替换整个课程Dictionary<TKey, TValue>,所以我想,您不仅感兴趣add方法,但get方法也更快。在这种情况下,下一个项目符号可能会被视为针对特定关键数据模式的更快解决方案。

  2. 然后DictionarySortedList(int)只能是数组Array<String>的纯静态/动态通用类型...但是这是BIG O( N):时间/空间。

说明: a.1)Dictionary可以在O(1)中使用get值(如果哈希值没有太多冲突!) a.2)Dictionary add有时是O(1),有时是O(n)。因此,如果您一个接一个地添加一个项目,那么对于下一个等于下一个质数的元素索引,您将收到一个时间复杂度O(n),该复杂度只是0(1)而已。来源:Understanding Generic Dictionary in-depth

b.1)Array可以通过预分配内存段中的int索引值简单地访问... Array[Index](时间复杂度= O(1))。 因此,对于dictionaryLoopSearchInEntryListTargetElement(TransformToBucketArrayIndex(GetHashCode()))

,它总是比以下操作更快 在发生冲突的情况下,

条目列表可以从1个周期重复到100个周期。

b.2)将值设置为Array仅仅是存储器中的int类型值分配操作(时间复杂度O(1))。 对于Dictionary,有时需要调整大小和/或重新组织。

在您的情况下:如果您知道键字符串的所有不同值都不多,则一些uint.MaxValue(无符号32位整数)(在32位环境中),并且“任意键的字符串的最大长度”不是然后再 4 (假设字符集从char(0)到char(255))->您可以轻松地将任何类型的String转换为相应的int值(用作Array<string>中的索引,以尽可能快的方式写入或读取String值。

获取和/或分配数组中的值始终是O(1)时间复杂度。 (Contains(TKey)可以写为TKeyValueArray[index] != NULL!注意:如果您的场景中TValues也可以为null,则创建一个类似于KeyValuePair的自定义类或通用类型的结构,但要额外添加boolean字段-标志设置或未设置)

粗略示例(提示):获取字节码,并对字符串索引[0、1、2、3]中的每个char字节码执行简单的数学运算

(
      index =
          SomeKeyString [ 0 ] * 256 * 256 * 256
        + SomeKeyString [ 1 ] * 256 * 256
        + SomeKeyString [ 2 ] * 256
        + SomeKeyString [ 3 ] 
)

可以根据情况优化公式和方法(如果字符串仅包含拉丁1字母字符,则无需使用过多的内存,也可以在数组中表示更长的TKey字符串)。 这是在迫切需要性能的情况下。

* 拉丁1字母使用 191个字符 ISO 8859-1对所谓的“ 1号拉丁字母”进行编码,由拉丁文字中的191个字符组成... *

很抱歉,我们没有提供详尽的提示,如果有兴趣,我会尝试提供更详细的答案。

也请阅读 Initial capacity of collection types, e.g. Dictionary, List