将KeyValuePair作为关键字的Dictionary(C#.NET)的Abysmal性能

本文关键字:NET 性能 Abysmal Dictionary KeyValuePair 关键字 | 更新日期: 2023-09-27 18:21:59

在我正在编写的应用程序中,我有两个潜在的大数据集需要相互映射。一个是从web服务返回的List,另一个是DataTable。我需要为列表中的每个项目获取ANSI(或ISO)编号,并在DataTable中找到包含该ANSI编号的行,然后对其进行处理。

由于DataTable.Select相当慢,而且我必须对列表中的每个项目都这样做,所以我尝试了更快的替代方案。请记住,DataTable对象没有数据库。所以我不能利用任何SQL功能或类似的功能。

我认为最快的方法可能是创建一个带有KeyValuePair(a:Ansi数字或I:Iso数字)的字典,并将其用作键。该值将是该行的其余部分。创建该字典显然需要一点处理时间,但我可以利用字典的快速搜索时间来找到我需要的每一行,然后将行添加回表中。因此,在foreach循环中,对于字典,我的复杂度只有O(1),而不是O(n)或DataTable.Select所具有的任何复杂度。

令我惊讶的是,这本字典竟然慢得令人难以置信。我不知道为什么,直到我发现使用字符串(只是ANSI数字)而不是KeyValuePair可以显著提高性能。我说话的速度快了几百倍。这究竟是怎么可能的?以下是我的测试方法:

我生成了一个List,用于模拟web服务的输出。我基于该列表创建了一个字典,其中包含一个键(字符串或KeyValuePair)和DataRow作为值。我遍历该列表的foreach循环,在字典中搜索该列表中的每个项,然后为返回的DataRow分配一个值。就是这样。

如果我使用KeyValuePair作为键来访问字典,1000个项目需要几秒钟的时间,如果我修改字典只使用字符串作为键,10000个项目需要毫秒的时间。仅供参考:我设计这个测试是为了总是有点击,所以所有的密钥都能找到。

这是我测量时间的代码块:

foreach(ProductList.Products item in pList.Output.Products)
{
   //KeyValuePair<string, string> kv = new KeyValuePair<string, string>("A", item.Ansi);
   DataRow row = dict[item.Ansi];
   for (int i = 0; i < 10; i++)
   {
      row["Material"] = item.Material + "a"; //Do stuff just for debugging
   }
   hits++;
}

那么,如果我使用字典(KeyValuePair,DataRow)而不是字典(String,DataRow),执行时间怎么会突然变长数百倍呢?

将KeyValuePair作为关键字的Dictionary(C#.NET)的Abysmal性能

KeyValuePair<TKey, TValue>没有实现GetHashCode()方法。这意味着,有意义地组织字典的唯一方法已经不复存在,剩下的是低效的线性搜索。

这并不奇怪,因为它不是KeyValuePair<TKey, TValue>的设计目的——它是字典使用的内部结构,而不是密钥。不要求.NET对象是有用的键,并且从所有GetHashCode()调用中返回0是完全有效的。

如果您不想使用自己的结构,请使用Tuple。但我真的会为任何形式的坚持创造自己的结构,真的。

顺便说一句,DataTable.Select的设计速度实际上相当快——为输出过滤数据。不过,它并不是为在一个循环中被调用数百次而设计的——开销占主导地位。当然,这是假设您有适当的索引。在您的情况下,我认为每次调用Select时都会重新生成索引,这有点慢:)

您可能会遇到大量与键值对的哈希冲突。您可以使用GetHashCode进行测试。

下面的链接是元组,但我强烈怀疑您对键值对也有同样的情况。gethashcode高重复率我会标记为重复,但你们很多人都有其他事情发生。

在此链接中,Microsoft建议不要对键使用值类型。KVP的GetHashCode是从值类型继承的。