我试图通过在 C# 中实现 Peter Norvig 的 spelling corrector 来更多地了解 LINQ。

第一部分涉及取一个大的 file of words(约 100 万个)并将其放入字典中,其中 key 是单词,0x2518122231343141 是出现次数。

我通常会这样做:

foreach (var word in allWords)
{
    if (wordCount.ContainsKey(word))
        wordCount[word]++;
    else
        wordCount.Add(word, 1);
}

其中 valueallWords
在 LINQ 中,我目前正在这样做:
var wordCountLINQ = (from word in allWordsLINQ
                         group word by word
                         into groups
                         select groups).ToDictionary(g => g.Key, g => g.Count());

我通过查看所有 IEnumerable<string> 来比较这两个字典,它们是相同的,因此它们产生相同的结果。
<key, value> 循环需要 3.82 秒 和 LINQ 查询需要 4.4925 秒 4.4925 秒

我正在使用 Stopwatch 类对其计时,并且以 RELEASE 模式运行。我不认为性能很差,我只是想知道是否存在差异的原因。

我是在以低效的方式执行 LINQ 查询还是遗漏了什么?

更新: 这是完整的基准代码示例:
public static void TestCode()
{
    //File can be downloaded from http://norvig.com/big.txt and consists of about a million words.
    const string fileName = @"path_to_file";
    var allWords = from Match m in Regex.Matches(File.ReadAllText(fileName).ToLower(), "[a-z]+", RegexOptions.Compiled)
                   select m.Value;

    var wordCount = new Dictionary<string, int>();
    var timer = new Stopwatch();
    timer.Start();
    foreach (var word in allWords)
    {
        if (wordCount.ContainsKey(word))
            wordCount[word]++;
        else
            wordCount.Add(word, 1);
    }
    timer.Stop();

    Console.WriteLine("foreach loop took {0:0.00} ms ({1:0.00} secs)\n",
            timer.ElapsedMilliseconds, timer.ElapsedMilliseconds / 1000.0);

    //Make LINQ use a different Enumerable (with the exactly the same values),
    //if you don't it suddenly becomes way faster, which I assmume is a caching thing??
    var allWordsLINQ = from Match m in Regex.Matches(File.ReadAllText(fileName).ToLower(), "[a-z]+", RegexOptions.Compiled)
                   select m.Value;

    timer.Reset();
    timer.Start();
    var wordCountLINQ = (from word in allWordsLINQ
                            group word by word
                            into groups
                            select groups).ToDictionary(g => g.Key, g => g.Count());
    timer.Stop();

    Console.WriteLine("LINQ took {0:0.00} ms ({1:0.00} secs)\n",
            timer.ElapsedMilliseconds, timer.ElapsedMilliseconds / 1000.0);
}

最佳答案

LINQ 版本较慢的原因之一是因为创建了两个字典而不是一个字典:

  • (内部)来自group by operator; group by 还存储每个单独的单词。您可以通过查看 ToArray() 而不是 Count() 来验证这一点。这是您的案例中实际上不需要的大量开销。
  • ToDictionary 方法基本上是对实际 LINQ 查询的 foreach,将查询结果添加到新字典中。根据唯一词的数量,这也可能需要一些时间。

  • LINQ 查询速度稍慢的另一个原因是,LINQ 依赖于 lambda 表达式(Dathan 的答案中的委托(delegate)),并且与内联代码相比,调用委托(delegate)会增加少量开销。

    编辑: 请注意,对于某些 LINQ 场景(例如 LINQ to SQL,但不是内存中 LINQ,例如此处),重写查询会产生更优化的计划:
    from word in allWordsLINQ
    group word by word into groups
    select new { Word = groups.Key, Count = groups.Count() }
    

    但是请注意,这不会为您提供字典,而是为您提供一系列单词及其计数。您可以将其转换为字典
    (from word in allWordsLINQ
     group word by word into groups
     select new { Word = groups.Key, Count = groups.Count() })
    .ToDictionary(g => g.Word, g => g.Count);
    

    关于.net - 将项目添加到字典的 LINQ 方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2118671/

    10-17 00:48