diff --git a/src/benchmarks/micro/libraries/System.Collections/Contains/DictionaryContainsKey.cs b/src/benchmarks/micro/libraries/System.Collections/Contains/DictionaryContainsKey.cs new file mode 100644 index 00000000000..257035a3773 --- /dev/null +++ b/src/benchmarks/micro/libraries/System.Collections/Contains/DictionaryContainsKey.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace System.Collections +{ + [BenchmarkCategory(Categories.Libraries, Categories.Collections, Categories.GenericCollections)] + [GenericTypeArguments(typeof(int))] // value type + [GenericTypeArguments(typeof(string))] // reference type + [GenericTypeArguments(typeof(Guid))] // larger value type + public class DictionaryContainsKey + { + private T[] _found; + private T[] _notFound; + private Dictionary _dictionary; + + [Params(Utils.DefaultCollectionSize)] + public int Size; + + [GlobalSetup] + public void Setup() + { + var allKeys = ValuesGenerator.ArrayOfUniqueValues(Size * 2); + _found = allKeys.Skip(Size).Take(Size).ToArray(); + _notFound = allKeys.Take(Size).ToArray(); + _dictionary = _found.ToDictionary(k => k, k => default(T)!); + } + + [Benchmark] + public bool ContainsKeyTrue() + { + bool result = default; + Dictionary collection = _dictionary; + T[] found = _found; + for (int i = 0; i < found.Length; i++) + result ^= collection.ContainsKey(found[i]); + return result; + } + + [Benchmark] + public bool ContainsKeyFalse() + { + bool result = default; + Dictionary collection = _dictionary; + T[] notFound = _notFound; + for (int i = 0; i < notFound.Length; i++) + result ^= collection.ContainsKey(notFound[i]); + return result; + } + } + + /// + /// Measures ContainsKey on a 1M-entry dictionary (~20 MB) to capture behavior + /// when the hash table far exceeds L1/L2 cache. Probes 8192 keys per invocation + /// for realistic cache-miss pressure while keeping per-call time low enough + /// for stable BDN statistics (~1-2% noise). + /// + [BenchmarkCategory(Categories.Libraries, Categories.Collections, Categories.GenericCollections)] + public class DictionaryContainsKeyLarge + { + private const int DictSize = 1_000_000; + private const int ProbeCount = 8192; + + private int[] _found; + private int[] _notFound; + private Dictionary _dictionary; + + [GlobalSetup] + public void Setup() + { + var allKeys = ValuesGenerator.ArrayOfUniqueValues(DictSize + ProbeCount); + var inDict = allKeys.Take(DictSize).ToArray(); + _found = inDict.Take(ProbeCount).ToArray(); + _notFound = allKeys.Skip(DictSize).Take(ProbeCount).ToArray(); + _dictionary = inDict.ToDictionary(k => k, k => k); + } + + [Benchmark] + public bool ContainsKeyTrue() + { + bool result = default; + var collection = _dictionary; + var found = _found; + for (int i = 0; i < found.Length; i++) + result ^= collection.ContainsKey(found[i]); + return result; + } + + [Benchmark] + public bool ContainsKeyFalse() + { + bool result = default; + var collection = _dictionary; + var notFound = _notFound; + for (int i = 0; i < notFound.Length; i++) + result ^= collection.ContainsKey(notFound[i]); + return result; + } + } +} diff --git a/src/benchmarks/micro/libraries/System.Collections/Contains/HashSetContains.cs b/src/benchmarks/micro/libraries/System.Collections/Contains/HashSetContains.cs new file mode 100644 index 00000000000..1347cb4b801 --- /dev/null +++ b/src/benchmarks/micro/libraries/System.Collections/Contains/HashSetContains.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace System.Collections +{ + [BenchmarkCategory(Categories.Libraries, Categories.Collections, Categories.GenericCollections)] + [GenericTypeArguments(typeof(int))] // value type + [GenericTypeArguments(typeof(string))] // reference type + [GenericTypeArguments(typeof(Guid))] // larger value type + public class HashSetContains + { + private T[] _found; + private T[] _notFound; + private HashSet _hashSet; + + [Params(Utils.DefaultCollectionSize)] + public int Size; + + [GlobalSetup] + public void Setup() + { + var allKeys = ValuesGenerator.ArrayOfUniqueValues(Size * 2); + _found = allKeys.Skip(Size).Take(Size).ToArray(); + _notFound = allKeys.Take(Size).ToArray(); + _hashSet = new HashSet(_found); + } + + [Benchmark] + public bool ContainsTrue() + { + bool result = default; + HashSet collection = _hashSet; + T[] found = _found; + for (int i = 0; i < found.Length; i++) + result ^= collection.Contains(found[i]); + return result; + } + + [Benchmark] + public bool ContainsFalse() + { + bool result = default; + HashSet collection = _hashSet; + T[] notFound = _notFound; + for (int i = 0; i < notFound.Length; i++) + result ^= collection.Contains(notFound[i]); + return result; + } + } +} diff --git a/src/benchmarks/micro/libraries/System.Collections/Dictionary/DictionaryTryRemove.cs b/src/benchmarks/micro/libraries/System.Collections/Dictionary/DictionaryTryRemove.cs new file mode 100644 index 00000000000..27461911168 --- /dev/null +++ b/src/benchmarks/micro/libraries/System.Collections/Dictionary/DictionaryTryRemove.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace System.Collections +{ + [BenchmarkCategory(Categories.Libraries, Categories.Collections, Categories.GenericCollections)] + [GenericTypeArguments(typeof(int), typeof(int))] // small value type key+value + [GenericTypeArguments(typeof(string), typeof(string))] // reference type key+value + [GenericTypeArguments(typeof(Guid), typeof(int))] // larger value type key + public class DictionaryTryRemove + { + private Dictionary _dictionary; + private TKey[] _keys; + + [Params(Utils.DefaultCollectionSize)] + public int Size; + + [GlobalSetup] + public void Setup() + { + _keys = ValuesGenerator.ArrayOfUniqueValues(Size); + _dictionary = _keys.ToDictionary(k => k, k => default(TValue)!); + } + + [Benchmark] + public bool TryRemove_Hit() + { + var dict = _dictionary; + var keys = _keys; + bool result = false; + for (int i = 0; i < keys.Length; i++) + { + result = dict.Remove(keys[i], out _); + dict.Add(keys[i], default!); + } + return result; + } + } +} diff --git a/src/benchmarks/micro/libraries/System.Collections/Remove/RemoveFalse.cs b/src/benchmarks/micro/libraries/System.Collections/Remove/RemoveFalse.cs new file mode 100644 index 00000000000..8f1455b5585 --- /dev/null +++ b/src/benchmarks/micro/libraries/System.Collections/Remove/RemoveFalse.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace System.Collections +{ + [BenchmarkCategory(Categories.Libraries, Categories.Collections, Categories.GenericCollections)] + [GenericTypeArguments(typeof(int))] // value type + [GenericTypeArguments(typeof(string))] // reference type + [GenericTypeArguments(typeof(Guid))] // larger value type + public class RemoveFalse + { + private T[] _missingKeys; + private HashSet _hashSet; + private Dictionary _dictionary; + + [Params(Utils.DefaultCollectionSize)] + public int Size; + + private T[] Setup() + { + var allKeys = ValuesGenerator.ArrayOfUniqueValues(Size * 2); + _missingKeys = allKeys.Take(Size).ToArray(); + return allKeys.Skip(Size).Take(Size).ToArray(); + } + + [GlobalSetup(Target = nameof(HashSet))] + public void SetupHashSet() => _hashSet = new HashSet(Setup()); + + [Benchmark] + public bool HashSet() + { + bool result = false; + var collection = _hashSet; + var keys = _missingKeys; + for (int i = 0; i < keys.Length; i++) + result = collection.Remove(keys[i]); + return result; + } + + [GlobalSetup(Target = nameof(Dictionary))] + public void SetupDictionary() + { + var source = Setup(); + _dictionary = source.ToDictionary(k => k, k => default(T)!); + } + + [Benchmark] + public bool Dictionary() + { + bool result = false; + var collection = _dictionary; + var keys = _missingKeys; + for (int i = 0; i < keys.Length; i++) + result = collection.Remove(keys[i]); + return result; + } + } +} diff --git a/src/benchmarks/micro/libraries/System.Collections/Remove/RemoveTrue.cs b/src/benchmarks/micro/libraries/System.Collections/Remove/RemoveTrue.cs new file mode 100644 index 00000000000..0f5ce48b49a --- /dev/null +++ b/src/benchmarks/micro/libraries/System.Collections/Remove/RemoveTrue.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace System.Collections +{ + [BenchmarkCategory(Categories.Libraries, Categories.Collections, Categories.GenericCollections)] + [GenericTypeArguments(typeof(int))] // value type + [GenericTypeArguments(typeof(string))] // reference type + [GenericTypeArguments(typeof(Guid))] // larger value type + public class RemoveTrue + { + private T[] _keys; + private HashSet _hashSet; + private Dictionary _dictionary; + + [Params(Utils.DefaultCollectionSize)] + public int Size; + + [GlobalSetup(Target = nameof(HashSet))] + public void SetupHashSet() + { + _keys = ValuesGenerator.ArrayOfUniqueValues(Size); + _hashSet = new HashSet(_keys); + } + + [Benchmark] + public bool HashSet() + { + bool result = false; + var collection = _hashSet; + var keys = _keys; + for (int i = 0; i < keys.Length; i++) + { + result = collection.Remove(keys[i]); + collection.Add(keys[i]); + } + return result; + } + + [GlobalSetup(Target = nameof(Dictionary))] + public void SetupDictionary() + { + _keys = ValuesGenerator.ArrayOfUniqueValues(Size); + _dictionary = _keys.ToDictionary(k => k, k => default(T)!); + } + + [Benchmark] + public bool Dictionary() + { + bool result = false; + var collection = _dictionary; + var keys = _keys; + for (int i = 0; i < keys.Length; i++) + { + result = collection.Remove(keys[i]); + collection.Add(keys[i], default!); + } + return result; + } + } +}