Метод LINQ для добавления элементов в словарь
Я пытаюсь узнать немного больше о LINQ путем реализации орфографический корректор в C#.
первая часть включает в себя прием большого слова (около 1 миллиона) и положить его в словарь, где key
- это слово и value
- число вхождений.
обычно я делаю это так:
foreach (var word in allWords)
{
if (wordCount.ContainsKey(word))
wordCount[word]++;
else
wordCount.Add(word, 1);
}
здесь allWords
это IEnumerable<string>
в LINQ я в настоящее время делаю он такой:
var wordCountLINQ = (from word in allWordsLINQ
group word by word
into groups
select groups).ToDictionary(g => g.Key, g => g.Count());
я сравниваю 2 словаря, глядя на все <key, value>
и они идентичны, поэтому они дают те же результаты.
на foreach
цикл занимает 3.82 сек и запрос LINQ принимает 4.49 сек
я синхронизирую его с помощью класса секундомера, и я работаю в режиме выпуска. Я не думаю, что производительность плохая, мне просто интересно, есть ли причина для разницы.
Am Я делаю запрос 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);
}
4 ответов
одна из причин, по которой версия LINQ медленнее, заключается в том, что вместо одного словаря создаются два словаря:
(внутренне) от оператора group by; группа by также хранит каждое отдельное слово. Вы можете проверить это, посмотрев на ToArray (), а не на Count (). Это много накладных расходов, которые вам на самом деле не нужны в вашем случае.
метод ToDictionary в основном является foreach над фактическим запросом LINQ, где результаты запроса добавляются в новый словарь. В зависимости от количества уникальных слов, это также может занять некоторое время.
еще одна причина, по которой запрос LINQ немного медленнее, заключается в том, что LINQ полагается на лямбда-выражения (делегат в ответе Дафана), и вызов делегата добавляет небольшое количество накладных расходов по сравнению со встроенным кодом.
Edit: обратите внимание, что для некоторых сценариев 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);
когда я создаю ваш второй пример, а затем открываю его в представлении разборки Reflector, я получаю следующее:
Dictionary<string, int> wordCountLINQ = allWordsLINQ.GroupBy<string, string>(delegate (string word) {
return word;
}).Select<IGrouping<string, string>, IGrouping<string, string>>(delegate (IGrouping<string, string> groups) {
return groups;
}).ToDictionary<IGrouping<string, string>, string, int>(delegate (IGrouping<string, string> g) {
return g.Key;
}, delegate (IGrouping<string, string> g) {
return g.Count<string>();
});
вероятно, это занимает больше времени только потому, что происходит больше вызовов функций, и в течение миллиона итераций, которые складываются.
полностью злоупотребляя LINQ, я смог заставить его быть примерно таким же и часто немного быстрее, чем цикл foreach, даже с вызовом делегата:
var wordCountLINQ = allWordsLINQ.Aggregate(new Dictionary<string, int>(), (wcld, w) => { wcld[w] = (wcld.ContainsKey(w) ? wcld[w] : 0) + 1; return wcld; })
не менял foreach
использование аналогичного выражения set не сделало его быстрее.
вы можете решить вашу проблему, используя лямбда-выражение:
var words = unitOfWork.DepartmentRepository.Get()
.GroupBy(a=>a.word).Select(s => new
{
Word = s.Key,
Count = s.Count()
}).ToDictionary(d=>d.Word, d=>d.Count);