using System.Collections.Immutable; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using LanguageExt; using PersistentMap; namespace AgainstLanguageExt; // Mocking your library types for the sake of the example to ensure compilation. // Replace these with your actual namespace imports. [MemoryDiagnoser] [HideColumns("Ratio", "RatioSD", "Alloc Ratio")] public class MapBenchmarks { [Params(10, 100, 1000)] public int KeyLength { get; set; } // Key length [Params(1000, 100000, 1000000)] public int CollectionSize { get; set; } // Data Source private KeyValuePair[] _data; private string[] _searchKeys; private int _index = 0; private int _mask; // Comparison Targets (for Lookup) private ImmutableDictionary _sysDict; private ImmutableSortedDictionary _sysSorted; private LanguageExt.HashMap _langExtHash; private LanguageExt.Map _langExtSorted; // Map is Sorted in LangExt private PersistentMap _persistentMap; [GlobalSetup] public void Setup() { var random = new Random(42); var dict = new Dictionary(); // 1. Generate Data while (dict.Count < CollectionSize) { string key = GenerateRandomString(random, KeyLength); if (!dict.ContainsKey(key)) { dict[key] = random.Next(); } } _data = dict.ToArray(); _searchKeys = dict.Keys.ToArray(); // 2. Pre-build maps for the Lookup benchmarks _sysDict = dict.ToImmutableDictionary(); _sysSorted = dict.ToImmutableSortedDictionary(); _langExtHash = Prelude.toHashMap(dict); _langExtSorted = Prelude.toMap(dict); // Build PersistentMap for lookup test var trans = PersistentMap.Empty(new UnicodeStrategy()).ToTransient(); foreach (var kvp in _data) { trans.Set(kvp.Key, kvp.Value); } _persistentMap = trans.ToPersistent(); } // ========================================== // BUILD BENCHMARKS // ========================================== [Benchmark(Description = "Lookup: PersistentMap (Cyclic)")] public int Lookup_Persistent_Cyclic() { // Fast wrap-around var key = _searchKeys[_index++ & _mask]; _persistentMap.TryGetValue(key, out var value); return value; } [Benchmark(Description = "Lookup: Sys.Sorted (Cyclic)")] public int Lookup_SysSorted_Cyclic() { var key = _searchKeys[_index++ & _mask]; _sysSorted.TryGetValue(key, out var value); return value; } [Benchmark(Description = "Build: Sys.ImmutableDict")] public ImmutableDictionary Build_SysImmutable() { // Using CreateRange/ToImmutable is usually the standard 'bulk' build return _data.ToImmutableDictionary(); } [Benchmark(Description = "Build: LangExt.HashMap")] public LanguageExt.HashMap Build_LangExtHash() { return Prelude.toHashMap(_data); } [Benchmark(Description = "Build: LangExt.SortedMap")] public LanguageExt.Map Build_LangExtSorted() { return Prelude.toMap(_data); } [Benchmark(Description = "Build: PersistentMap (Iterative)")] public PersistentMap Build_Persistent_Iterative() { // Simulating naive immutable building (O(n log n) or worse due to copying) var map = PersistentMap.Empty(new UnicodeStrategy()); foreach (var item in _data) { map = map.Set(item.Key, item.Value); } return map; } [Benchmark(Description = "Build: PersistentMap (Transient)")] public PersistentMap Build_Persistent_Transient() { // Simulating efficient mutable build -> freeze var trans = PersistentMap.Empty(new UnicodeStrategy()).ToTransient(); foreach (var item in _data) { trans.Set(item.Key, item.Value); } return trans.ToPersistent(); } // ========================================== // LOOKUP BENCHMARKS // ========================================== [Benchmark(Baseline = true, Description = "Lookup: Sys.ImmutableDict")] public int Lookup_SysImmutable() { var key = _searchKeys[CollectionSize / 2]; _sysDict.TryGetValue(key, out var value); return value; } [Benchmark(Description = "Lookup: Sys.SortedDict")] public int Lookup_SysSorted() { var key = _searchKeys[CollectionSize / 2]; _sysSorted.TryGetValue(key, out var value); return value; } [Benchmark(Description = "Lookup: LangExt.HashMap")] public int Lookup_LangExtHash() { var key = _searchKeys[CollectionSize / 2]; // LanguageExt often uses Find which returns Option, or [] operator // Assuming TryGetValue-like behavior or using match for fairness return _langExtHash.Find(key).IfNone(0); } [Benchmark(Description = "Lookup: LangExt.SortedMap")] public int Lookup_LangExtSorted() { var key = _searchKeys[CollectionSize / 2]; return _langExtSorted.Find(key).IfNone(0); } [Benchmark(Description = "Lookup: PersistentMap")] public int Lookup_PersistentMap() { var key = _searchKeys[CollectionSize / 2]; _persistentMap.TryGetValue(key, out var value); return value; } // Helper private string GenerateRandomString(Random rng, int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var buffer = new char[length]; for (int i = 0; i < length; i++) { buffer[i] = chars[rng.Next(chars.Length)]; } return new string(buffer); } } public class Program { public static void Main(string[] args) { // This scans the assembly and lets the command line (args) decide what to run BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } }