PersistentMap/benchmarks/AgainstLanguageExt/AgainstLanguageExt.cs

198 lines
6.2 KiB
C#
Raw Permalink Normal View History

2026-02-11 20:59:55 +01:00
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<string, int>[] _data;
private string[] _searchKeys;
private int _index = 0;
private int _mask;
// Comparison Targets (for Lookup)
private ImmutableDictionary<string, int> _sysDict;
private ImmutableSortedDictionary<string, int> _sysSorted;
private LanguageExt.HashMap<string, int> _langExtHash;
private LanguageExt.Map<string, int> _langExtSorted; // Map<K,V> is Sorted in LangExt
private PersistentMap<string, int, UnicodeStrategy> _persistentMap;
[GlobalSetup]
public void Setup()
{
var random = new Random(42);
var dict = new Dictionary<string, int>();
// 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<string, int, UnicodeStrategy>.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<string, int> Build_SysImmutable()
{
// Using CreateRange/ToImmutable is usually the standard 'bulk' build
return _data.ToImmutableDictionary();
}
[Benchmark(Description = "Build: LangExt.HashMap")]
public LanguageExt.HashMap<string, int> Build_LangExtHash()
{
return Prelude.toHashMap(_data);
}
[Benchmark(Description = "Build: LangExt.SortedMap")]
public LanguageExt.Map<string, int> Build_LangExtSorted()
{
return Prelude.toMap(_data);
}
[Benchmark(Description = "Build: PersistentMap (Iterative)")]
public PersistentMap<string, int, UnicodeStrategy> Build_Persistent_Iterative()
{
// Simulating naive immutable building (O(n log n) or worse due to copying)
var map = PersistentMap<string, int, UnicodeStrategy>.Empty(new UnicodeStrategy());
foreach (var item in _data)
{
map = map.Set(item.Key, item.Value);
}
return map;
}
[Benchmark(Description = "Build: PersistentMap (Transient)")]
public PersistentMap<string, int, UnicodeStrategy> Build_Persistent_Transient()
{
// Simulating efficient mutable build -> freeze
var trans = PersistentMap<string, int, UnicodeStrategy>.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);
}
}