【发布时间】:2021-09-19 01:13:07
【问题描述】:
所以我正在尝试过滤 c# .NET5 中的列表,并且我想专注于该过程的性能。目前该程序正在为此使用 ForEach 循环,这很糟糕。
无论如何,这就是示例,我得到一个字符串列表,其中可以包含任何类型的字符串,包括 null 或空。有 10k 个条目。
我找到了使用 LINQ 的过滤方法,例如.Where 或 .RemoveAll,但您喜欢过滤。
首先我认为最好和最快的方法是制作一个哈希集并使用 List.RemoveAll(i => hashset.Contains(i)) 因为我所有的研究都指出使用 hashset.Contains 迭代 List 是最快的方法。我还认为 RemoveAll 会更快,因为 Contains-Method 的检查会在它返回一个“真”案例时立即停止。
我创建了一个fiddle 来对所有这些方法进行基准测试,但问题是,我得到了这些结果:
.Where & HashSet contains: 0.7018 MS
.RemoveAll & HashSet contains: 0.4803 MS
.Where & List contains: 0.0072 MS
.RemoveAll & List contains: 0.4504 MS
.Where 0.1234 MS
.RemoveAll 0.0006 MS
ForEach 41.8379 MS
这些结果提出了新的问题,希望我能得到解释或只是一个明确的答案。
问题:
- 不出所料,.RemoveAll & HashSet 比 .Where & HashSet 快,但这不适用于 List.Contains,为什么?
- 为什么通过 List.Contains 得到的结果比两种 HashSet 方法都快?尤其是 .Where & List
- 为什么是普通的 List.Where,它总是比两种 HashSet 方法更快地检查 4 个语句是否为真?
- 我预计 List.RemoveAll 会很快,但没想到会那么快...我查找的大多数搜索和问题,人们总是建议在这种情况下使用 HashSet。
我希望有人能解释一下,至少部分解释一下。
编辑:
我发现,最后一个 .RemoveAll 方法是我做的一个错误,我使用了 .Where 而不是 .RemoveAll,现在事情变得更加清晰了。我还删除了 ForEach 循环中的 .ToLower 函数
现在我得到了这些结果:
.Where & HashSet contains: 0.6769 MS
.RemoveAll & HashSet contains: 0.4603 MS
.Where & List contains: 0.0052 MS
.RemoveAll & List contains: 0.449 MS
.Where 0.1103 MS
.RemoveAll 0.305 MS
ForEach 0.4445 MS
但我仍然要问,为什么在使用 .Where 时 HashSet 比 List 花费的时间更长。 还有为什么在使用 HashSet 时 RemoveAll 比 Where 快,而 List 却相反?
【问题讨论】:
-
物化这些列表。在您实际枚举它们的结果之前,大多数 LINQ 函数都不会被评估。话虽如此:设置一个微基准比这更复杂(至少你必须重复每个测试数千次,然后平均 - 在去除异常值之后)
-
你在比较苹果和橘子。
RemoveAll实际上修改了当前列表,而Where & List contains除了创建查询之外什么都不做。追加ToList并将其分配给原始列表,您可以比较结果(但由于您创建了一个新列表,结果仍然不同)。 -
也使用 BenchmarkDotnet,而不是更可靠的数字。
-
另外,
RemoveAll改变了input列表,因此所有后续测试都是垃圾 -
您根本无法将
Where与RemoveAll进行比较,因为Where正在使用延迟执行,所以它只是创建一个查询而不是一个列表(附加ToList以获得一个有意义的结果)。所以最后一个问题是为什么Where(HashSet.Contains)似乎比Where(List.Contains)慢得多。这就是为什么我说你应该使用BenchmarkDotNet。反转这两个测试,突然List.Contains会变慢。您需要对基准进行热身。无论如何,如果没有ToList,这些Where测试毫无价值。
标签: c# performance linq collections .net-5