包含、存在和任何的性能基准测试
Posted
技术标签:
【中文标题】包含、存在和任何的性能基准测试【英文标题】:Performance Benchmarking of Contains, Exists and Any 【发布时间】:2013-09-10 05:01:42 【问题描述】:我一直在寻找Contains
、Exists
和Any
方法之间的性能基准测试,这些方法在List<T>
中可用。我只是出于好奇才想知道这一点,因为我总是对这些感到困惑。许多关于 SO 的问题都描述了这些方法的定义,例如:
-
LINQ Ring: Any() vs Contains() for Huge Collections
Linq .Any VS .Exists - Whats the difference?
LINQ extension methods - Any() vs. Where() vs. Exists()
所以我决定自己做。我将其添加为答案。任何对结果的更多见解都是最受欢迎的。我还对数组进行了此基准测试以查看结果
【问题讨论】:
【参考方案1】:最快的方法是使用HashSet
。
Contains
的 HashSet
是 O(1)。
我获取了您的代码并为HashSet<int>
添加了基准
HashSet<int> set = new HashSet<int>(list);
的性能成本几乎为零。
代码
void Main()
ContainsExistsAnyVeryShort();
ContainsExistsAnyShort();
ContainsExistsAny();
private static void ContainsExistsAny()
Console.WriteLine("***************************************");
Console.WriteLine("********* ContainsExistsAny ***********");
Console.WriteLine("***************************************");
List<int> list = new List<int>(6000000);
Random random = new Random();
for (int i = 0; i < 6000000; i++)
list.Add(random.Next(6000000));
int[] arr = list.ToArray();
HashSet<int> set = new HashSet<int>(list);
find(list, arr, set, (method, stopwatch) => $"method: stopwatch.ElapsedMillisecondsms");
private static void ContainsExistsAnyShort()
Console.WriteLine("***************************************");
Console.WriteLine("***** ContainsExistsAnyShortRange *****");
Console.WriteLine("***************************************");
List<int> list = new List<int>(2000);
Random random = new Random();
for (int i = 0; i < 2000; i++)
list.Add(random.Next(6000000));
int[] arr = list.ToArray();
HashSet<int> set = new HashSet<int>(list);
find(list, arr, set, (method, stopwatch) => $"method: stopwatch.ElapsedMillisecondsms");
private static void ContainsExistsAnyVeryShort()
Console.WriteLine("*******************************************");
Console.WriteLine("***** ContainsExistsAnyVeryShortRange *****");
Console.WriteLine("*******************************************");
List<int> list = new List<int>(10);
Random random = new Random();
for (int i = 0; i < 10; i++)
list.Add(random.Next(6000000));
int[] arr = list.ToArray();
HashSet<int> set = new HashSet<int>(list);
find(list, arr, set, (method, stopwatch) => $"method: stopwatch.ElapsedTicks ticks");
private static void find(List<int> list, int[] arr, HashSet<int> set, Func<string, Stopwatch, string> format)
Random random = new Random();
int[] find = new int[10000];
for (int i = 0; i < 10000; i++)
find[i] = random.Next(6000000);
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
list.Contains(find[rpt]);
watch.Stop();
Console.WriteLine(format("List/Contains", watch));
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
list.Exists(a => a == find[rpt]);
watch.Stop();
Console.WriteLine(format("List/Exists", watch));
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
list.Any(a => a == find[rpt]);
watch.Stop();
Console.WriteLine(format("List/Any", watch));
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
arr.Contains(find[rpt]);
watch.Stop();
Console.WriteLine(format("Array/Contains", watch));
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
Array.Exists(arr, a => a == find[rpt]);
watch.Stop();
Console.WriteLine(format("Array/Exists", watch));
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
Array.IndexOf(arr, find[rpt]);
watch.Stop();
Console.WriteLine(format("Array/IndexOf", watch));
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
arr.Any(a => a == find[rpt]);
watch.Stop();
Console.WriteLine(format("Array/Any", watch));
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
set.Contains(find[rpt]);
watch.Stop();
Console.WriteLine(format("HashSet/Contains", watch));
结果
*******************************************
***** ContainsExistsAnyVeryShortRange *****
*******************************************
List/Contains: 1067 ticks
List/Exists: 2884 ticks
List/Any: 10520 ticks
Array/Contains: 1880 ticks
Array/Exists: 5526 ticks
Array/IndexOf: 601 ticks
Array/Any: 13295 ticks
HashSet/Contains: 6629 ticks
***************************************
***** ContainsExistsAnyShortRange *****
***************************************
List/Contains: 4ms
List/Exists: 28ms
List/Any: 138ms
Array/Contains: 6ms
Array/Exists: 34ms
Array/IndexOf: 3ms
Array/Any: 96ms
HashSet/Contains: 0ms
***************************************
********* ContainsExistsAny ***********
***************************************
List/Contains: 11504ms
List/Exists: 57084ms
List/Any: 257659ms
Array/Contains: 11643ms
Array/Exists: 52477ms
Array/IndexOf: 11741ms
Array/Any: 194184ms
HashSet/Contains: 3ms
编辑 (2021-08-25)
我为非常短的集合(10 项)添加了比较,还添加了Array.Contains
和Array.IndexOf
。您可以看到 Array.IndexOf
对于如此小的范围来说是最快的。也就是说,因为正如@lucky-brian 所说,这里的 n 非常小,所以 for
-loop 比稍微复杂的搜索算法执行得更好。但是我仍然建议尽可能使用HashSet<T>
,因为它更好地反映了只有唯一值的意图,并且小集合的差异低于 1ms。
【讨论】:
我正在寻找这样的东西。当我看到 HashSet/Contains 上的表现时,我就像“Holy Molly”。肯定会在我的 1000 .. 5000 个项目的环境中尝试一下。 在这里也能看到 HashMap 的性能会很高兴。 你知道 HashSet 的 Any 是否也是 O(1) 吗? Any 在HashSet<T>
上不存在,但它是 IEnumerable<T>
的扩展方法,并且是 O(1) 没有谓词和 O(n) 有一个谓词。 Herse 是来源:github.com/Microsoft/referencesource/blob/master/System.Core/…
"数组不存在" ? Array.Exists(
docs.microsoft.com/en-us/dotnet/api/…【参考方案2】:
根据文档:
List.Exists(对象方法)
确定 List(T) 是否包含与 由指定谓词定义的条件。
IEnumerable.Any(扩展方法)
确定序列的任何元素是否满足条件。
List.Contains(对象方法)
确定一个元素是否在列表中。
基准测试:
代码:
static void Main(string[] args)
ContainsExistsAnyShort();
ContainsExistsAny();
private static void ContainsExistsAny()
Console.WriteLine("***************************************");
Console.WriteLine("********* ContainsExistsAny ***********");
Console.WriteLine("***************************************");
List<int> list = new List<int>(6000000);
Random random = new Random();
for (int i = 0; i < 6000000; i++)
list.Add(random.Next(6000000));
int[] arr = list.ToArray();
find(list, arr);
private static void ContainsExistsAnyShort()
Console.WriteLine("***************************************");
Console.WriteLine("***** ContainsExistsAnyShortRange *****");
Console.WriteLine("***************************************");
List<int> list = new List<int>(2000);
Random random = new Random();
for (int i = 0; i < 2000; i++)
list.Add(random.Next(6000000));
int[] arr = list.ToArray();
find(list, arr);
private static void find(List<int> list, int[] arr)
Random random = new Random();
int[] find = new int[10000];
for (int i = 0; i < 10000; i++)
find[i] = random.Next(6000000);
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
list.Contains(find[rpt]);
watch.Stop();
Console.WriteLine("List/Contains: 0:N0ms", watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
list.Exists(a => a == find[rpt]);
watch.Stop();
Console.WriteLine("List/Exists: 0:N0ms", watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
list.Any(a => a == find[rpt]);
watch.Stop();
Console.WriteLine("List/Any: 0:N0ms", watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
arr.Contains(find[rpt]);
watch.Stop();
Console.WriteLine("Array/Contains: 0:N0ms", watch.ElapsedMilliseconds);
Console.WriteLine("Arrays do not have Exists");
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 10000; rpt++)
arr.Any(a => a == find[rpt]);
watch.Stop();
Console.WriteLine("Array/Any: 0:N0ms", watch.ElapsedMilliseconds);
结果
***************************************
***** ContainsExistsAnyShortRange *****
***************************************
List/Contains: 96ms
List/Exists: 146ms
List/Any: 381ms
Array/Contains: 34ms
Arrays do not have Exists
Array/Any: 410ms
***************************************
********* ContainsExistsAny ***********
***************************************
List/Contains: 257,996ms
List/Exists: 379,951ms
List/Any: 884,853ms
Array/Contains: 72,486ms
Arrays do not have Exists
Array/Any: 1,013,303ms
【讨论】:
请记住,尽管 Contains 似乎是最快的,但 LINQ 2 SQL 在列表中限制为 ~2100 个对象,因此它适用于较短的列表。 @jyparask 即使对于较大的列表,包含似乎也不错。但是,我也更新了较短列表的代码和时间。结果如你所料。 我运行了你的基准测试,我发现 List.Exists 实际上比 List.Contains 略快,分别为 45 毫秒和 55 毫秒。其余的似乎与您的结果一致。在 .NET 4.5 上使用 Visual Studio 2013 在 32 位发布模式下进行了优化测试。 你是什么意思:“数组不存在”?我认为它有 Exists():***.com/a/22928748/4608491 "数组不存在" ?Array.Exists(
docs.microsoft.com/en-us/dotnet/api/…【参考方案3】:
值得一提的是,这种比较有点不公平,因为Array
类不拥有Contains()
方法。它通过连续的Enumerator
使用IEnumerable<T>
的扩展方法,因此它没有针对Array
实例进行优化。另一方面,HashSet<T>
有自己的实现,针对所有大小进行了全面优化。
为了公平比较,您可以使用为Array
实例实现的静态方法int Array.IndexOf()
,尽管它使用的for
循环比Enumerator
更有效。
使用公平的比较算法,HashSet<T>.Contains()
的最多 5 个元素的小集合的性能与 Array.IndexOf()
相似,但对于更大的集合更有效。
【讨论】:
以上是关于包含、存在和任何的性能基准测试的主要内容,如果未能解决你的问题,请参考以下文章