字典中的前100个值<;字符串,int>;-为什么LinQ比foreach循环快得多

本文关键字:LinQ 为什么 快得多 循环 foreach gt 100个值 lt 字典 字符串 int | 更新日期: 2023-09-27 18:19:39

我正在编写一个简单的应用程序来解析一个巨大的文本文件(60gb),并存储所有单词及其在文件中出现的时间。为了测试起见,我将文件缩减为2gb。

我在字典里有单词和计数,尽管我发现很难相信我看到的结果。

字典中的单词总数:1128495

我正在使用的代码:

sw.Start();
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<html><head></head><body>");
lock (Container.values)
{
    int i = int.Parse(ctx.Request.QueryString["type"]);
    switch (i)
    {
        case 1: //LinQ
            var values = Container.values.OrderByDescending(a => a.Value.Count).Take(100);
            foreach (var value in values)
            {
                sb.AppendFormat("{0} - {1}<br />", value.Key, value.Value.Count);
            }
            break;
        case 2: //Foreach
            foreach (var y in Container.values)
            {
            }
            break;
        case 3: //For
            for (int x = 0; x < Container.values.Count; x++)
            {
            }
            break;
    }                
}
sw.Stop();
sb.AppendFormat("<br /><br /> {0}", sw.ElapsedMilliseconds);
sb.AppendFormat("</body>");

运行两次,以下速度以毫秒为单位:

LinQ:#1:598,#2 609

Foreach:#1 1000,#1020

为什么LinQ比前臂还快?我认为LinQ必须循环遍历Dictionary本身,那么它是如何做到这一点的呢?

编辑:编译到Release模式后,结果如下:林:796(慢一点?)foreach:945

该应用程序是一个简单的控制台应用程序,代码在HttpListener 中执行

编辑2:我已经设法弄清楚问题出在哪里了。当我初始化字典时,我将其容量设置为89000000(当处理60gb文件时,它会抛出OutOfMemory异常)。由于某些原因,这大大降低了foreach循环的性能。如果我将容量设置为1128495,foreach循环将在56毫秒内执行。

为什么会发生这种情况?如果我在循环中放入一个计数器,即使容量为89000000,它也只运行1128495次。

字典中的前100个值<;字符串,int>;-为什么LinQ比foreach循环快得多

foreach循环由编译器通过调用GetEnumerator(),然后在枚举器上重复调用MoveNext和Current来实现。LINQ的OrderByDescending通常以完全相同的方式工作,它基本上做了一个foreach来提取所有元素,然后对它们进行排序。

在ILSpy中快速查看一下,OrderByDescending将容器放入一个名为Buffer<T>的内部类型中,该类型进行了优化:如果容器实现ICollection<T>,则使用ICollection<T>.CopyTo而不是foreach循环。通常OrderByDescending仍然不会比foreach循环快,因为在提取元素后,它必须对它们进行排序。

你是否遗漏了foreach循环中的代码,这些代码可以解释为什么它更慢?如果你真的在使用一个空的foreach循环,也许解释是Container.valuesIEnumerator<T>类型(或GetEnumerator方法)与其CopyTo方法相比速度较慢。

您的LINQ版本只接受前100个元素!

移除.Take(100)以便进行比较!