我试图用C#计算每个唯一密码在rockyou.txt文件中出现的次数,但由于我对算法很弱,我决定在Release配置中实现我能想到的每一种暴力方法(总共五种)和时间。
我对结果感到非常惊讶,并寻求解释为什么每种方法都比以前更快:
问题更新如下:
为了后验性,我发布了我的最新结果。
设置:
ConcurrentDictionary<string, int> conDict = new();
Dictionary<string, int> dict = new();
string[] lines = File.ReadAllLines(@"rockyou.txt");
IEnumerable<string> linesEnum = File.ReadLines(@"rockyou.txt");
第一种方法:
这里的想法是迭代每个密码(每一行),如果它存在于字典中,则增加它出现的次数。如果它不存在,它将触发一个异常,try-catch将确保密码被添加到字典中。
foreach (var line in lines)
{
try
{
dict[line] += 1;
}
catch
{
dict[line] = 1;
}
}
第二种方法:
foreach (var line in lines)
{
if (dict.ContainsKey(line))
dict[line] += 1;
else
dict[line] = 1;
}
第三种方法:
foreach (var line in lines)
conDict.AddOrUpdate(line, 1, (id, count) => count + 1);
第四种方法:
Parallel.ForEach(lines, line => conDict.AddOrUpdate(line, 1, (id, count) => count + 1));
第五种方法:
var res = lines.GroupBy(line => line).ToDictionary(group => group.Key, group => group.Count());
第六种方法:
var wordCounts = from w in lines
group w by w into g
select new { Word = g.Key, Count = g.Count() };
var result = wordCounts.ToList();
第七种方法:
var dict = linesEnum.GroupBy(line => line).ToDictionary(group => group.Key, group => group.Count());
第八种方法:
foreach (var line in lines)
dict[line] = (dict.TryGetValue(line, out var count) ? count : 0) + 1;
基准人员简历
方法 | 平均值 | 错误 | >StdDev right;">Gen 2 | 分配 |
---|---|---|---|---|
一个 | 2031.4毫秒 | 384 B | ||
两个 | 2176.9毫秒 | 96 B | ||
三个 | 351.7毫秒 | 384 B | ||
四个 | 548.0毫秒 | 23712 B | ||
五个 | 19952.9毫秒 | 2863095616 B | ||
六个 | 118398.0毫秒 | 22258200168 B | ||
Seven | 25000.0 ms | 3506923376 B | ||
八个 | 2059.4毫秒 | 96 B |
算法是一样的,到处都使用哈希表。到你的方法
- 不要使用异常来定义程序的标准流。他们在这方面异常缓慢
- 有点次优,应该使用linesEnum
- 您使用的是并发收集,而不需要并发。这会给你带来性能上的冲击
- 好的,如果你需要最大的性能
- 。。7.林克会给你一些开销,这项任务太琐碎了
- 好,但使用lineEnum
通常,如果您可以将文件作为管道处理,请执行此操作。它可以节省内存,也有助于提高速度。您还希望最大限度地减少对字典的访问次数,TryGetValue
通常就是您所需要的。从简单代码开始
foreach (var line in File.ReadLines(@"rockyou.txt"))
{
dict.TryGetValue(line, out var count);
dict[line] = count + 1;
}
最有可能的瓶颈是从文件中读取。如果是这样的话,你就完了。您可以使用并行处理,但除非从光盘读取的速度快于处理速度,否则不要指望有显著的改进。