using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using LanguageExt; using static LanguageExt.Prelude; using PersistentMap; [MemoryDiagnoser] [HideColumns("Ratio", "RatioSD", "Alloc Ratio")] public class CyclicMapBenchmarks { // Powers of 2 for fast bitwise masking [Params(1024, 131072)] public int CollectionSize { get; set; } [Params(10, 100, 1000)] public int N { get; set; } // Collections private PersistentMap _persistentMap; private ImmutableSortedDictionary _sysSorted; private LanguageExt.HashMap _langExtHash; private LanguageExt.Map _langExtSorted; // Lookup scaffolding private string[] _searchKeys; private int _index = 0; private int _mask; [GlobalSetup] public void Setup() { _mask = CollectionSize - 1; var random = new Random(42); var data = new Dictionary(); // 1. Generate Data while (data.Count < CollectionSize) { string key = GenerateRandomString(random, N); if (!data.ContainsKey(key)) { data[key] = random.Next(); } } // 2. Build Collections // PersistentMap var builder = PersistentMap.Empty(new UnicodeStrategy()).ToTransient(); foreach (var kvp in data) builder.Set(kvp.Key, kvp.Value); _persistentMap = builder.ToPersistent(); // System _sysSorted = data.ToImmutableSortedDictionary(); // LanguageExt _langExtHash = toHashMap(data); _langExtSorted = toMap(data); // 3. Setup Cyclic Keys _searchKeys = data.Keys.ToArray(); // Shuffle to defeat branch prediction / cache pre-fetching Random.Shared.Shuffle(_searchKeys); } [Benchmark(Description = "Cyclic: PersistentMap")] public int Lookup_Persistent() { var key = _searchKeys[_index++ & _mask]; _persistentMap.TryGetValue(key, out var value); return value; } [Benchmark(Description = "Cyclic: Sys.Sorted")] public int Lookup_SysSorted() { var key = _searchKeys[_index++ & _mask]; _sysSorted.TryGetValue(key, out var value); return value; } [Benchmark(Description = "Cyclic: LangExt.HashMap")] public int Lookup_LangExtHash() { var key = _searchKeys[_index++ & _mask]; // Option struct return, overhead is minimal but present return _langExtHash.Find(key).IfNone(0); } [Benchmark(Description = "Cyclic: LangExt.Sorted")] public int Lookup_LangExtSorted() { var key = _searchKeys[_index++ & _mask]; // AVL Tree traversal return _langExtSorted.Find(key).IfNone(0); } 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); } }