added proper benchmarking against others
changed StandardStrategy tonuse binary search. and ao on
This commit is contained in:
parent
9242c1c751
commit
c7c5c7b81b
9 changed files with 648 additions and 627 deletions
90
PersistentMap/KeyStrategies/IntScanner.cs
Normal file
90
PersistentMap/KeyStrategies/IntScanner.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
using System.Runtime.Intrinsics.X86;
|
||||||
|
|
||||||
|
namespace PersistentMap;
|
||||||
|
|
||||||
|
public static class IntScanner
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int FindFirstGreaterOrEqual(ReadOnlySpan<int> keys, int target)
|
||||||
|
{
|
||||||
|
// Fallback for short arrays or unsupported hardware.
|
||||||
|
// AVX2 processes 8 integers at a time.
|
||||||
|
if (!Avx2.IsSupported || keys.Length < 8)
|
||||||
|
return LinearScan(keys, target);
|
||||||
|
|
||||||
|
return Avx512F.IsSupported
|
||||||
|
? ScanAvx512(keys, target)
|
||||||
|
: ScanAvx2(keys, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int LinearScan(ReadOnlySpan<int> keys, int target)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < keys.Length; i++)
|
||||||
|
if (keys[i] >= target)
|
||||||
|
return i;
|
||||||
|
return keys.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static unsafe int ScanAvx2(ReadOnlySpan<int> keys, int target)
|
||||||
|
{
|
||||||
|
// AVX2 lacks a native GreaterOrEqual for 32-bit integers.
|
||||||
|
// We use GreaterThan(Data, target - 1).
|
||||||
|
var vTarget = Vector256.Create(target - 1);
|
||||||
|
var i = 0;
|
||||||
|
var len = keys.Length;
|
||||||
|
|
||||||
|
for (; i <= len - 8; i += 8)
|
||||||
|
{
|
||||||
|
fixed (int* ptr = keys)
|
||||||
|
{
|
||||||
|
var vData = Avx2.LoadVector256(ptr + i);
|
||||||
|
var vResult = Avx2.CompareGreaterThan(vData, vTarget);
|
||||||
|
|
||||||
|
// MoveMask creates a 32-bit integer from the most significant bit of each byte.
|
||||||
|
var mask = (uint)Avx2.MoveMask(vResult.AsByte());
|
||||||
|
|
||||||
|
if (mask != 0)
|
||||||
|
{
|
||||||
|
// Since an int is 4 bytes, MoveMask sets 4 bits per matching element.
|
||||||
|
// Dividing the trailing zero count by 4 maps the byte offset back to the integer index.
|
||||||
|
return i + (BitOperations.TrailingZeroCount(mask) / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LinearScan(keys.Slice(i), target) + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static unsafe int ScanAvx512(ReadOnlySpan<int> keys, int target)
|
||||||
|
{
|
||||||
|
// AVX-512 processes 16 integers (512 bits) per instruction.
|
||||||
|
var vTarget = Vector512.Create(target);
|
||||||
|
var i = 0;
|
||||||
|
var len = keys.Length;
|
||||||
|
|
||||||
|
for (; i <= len - 16; i += 16)
|
||||||
|
{
|
||||||
|
fixed (int* ptr = keys)
|
||||||
|
{
|
||||||
|
var vData = Avx512F.LoadVector512(ptr + i);
|
||||||
|
|
||||||
|
// Vector512 API is used directly here to cleanly get the mask
|
||||||
|
var mask = Vector512.GreaterThanOrEqual(vData, vTarget);
|
||||||
|
|
||||||
|
if (mask != Vector512<int>.Zero)
|
||||||
|
{
|
||||||
|
uint m = (uint)mask.ExtractMostSignificantBits();
|
||||||
|
return i + BitOperations.TrailingZeroCount(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LinearScan(keys.Slice(i), target) + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ public readonly struct StandardStrategy<K> : IKeyStrategy<K>
|
||||||
}
|
}
|
||||||
// Tell the B-Tree to skip SIMD routing and just use LinearSearch
|
// Tell the B-Tree to skip SIMD routing and just use LinearSearch
|
||||||
public bool UsesPrefixes => false;
|
public bool UsesPrefixes => false;
|
||||||
|
public bool UseBinarySearch => true;
|
||||||
// This will never be called because UsesPrefixes is false,
|
// This will never be called because UsesPrefixes is false,
|
||||||
// but we must satisfy the interface.
|
// but we must satisfy the interface.
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
|
||||||
170
TestProject1/FuzzTestStandardStrategy.cs
Normal file
170
TestProject1/FuzzTestStandardStrategy.cs
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using PersistentMap;
|
||||||
|
|
||||||
|
public class BTreeFuzzTestStandardStrategy
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _output;
|
||||||
|
private readonly StandardStrategy<int> _strategy = new();
|
||||||
|
|
||||||
|
public BTreeFuzzTestStandardStrategy(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
_output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Fuzz_Insert_And_Remove_consistency()
|
||||||
|
{
|
||||||
|
// CONFIGURATION
|
||||||
|
const int Iterations = 100_000; // High enough to trigger all splits/merges
|
||||||
|
const int KeyRange = 5000; // Small enough to cause frequent collisions
|
||||||
|
const bool showOps = false;
|
||||||
|
int Seed = 2135974; // Environment.TickCount;
|
||||||
|
|
||||||
|
// ORACLES
|
||||||
|
var reference = new SortedDictionary<int, int>();
|
||||||
|
var subject = BaseOrderedMap<int, int, StandardStrategy<int>>.CreateTransient(_strategy);
|
||||||
|
|
||||||
|
var random = new Random(Seed);
|
||||||
|
_output.WriteLine($"Starting Fuzz Test with Seed: {Seed}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Iterations; i++)
|
||||||
|
{
|
||||||
|
// 1. Pick an Action: 70% Insert/Update, 30% Remove
|
||||||
|
bool isInsert = random.NextDouble() < 0.7;
|
||||||
|
int key = random.Next(KeyRange);
|
||||||
|
int val = key * 100;
|
||||||
|
|
||||||
|
if (isInsert)
|
||||||
|
{
|
||||||
|
// ACTION: INSERT
|
||||||
|
if (showOps)Console.WriteLine($"insert: {key} : {val}");
|
||||||
|
if (key == 4436)
|
||||||
|
{
|
||||||
|
Console.WriteLine("BP");
|
||||||
|
}
|
||||||
|
|
||||||
|
reference[key] = val;
|
||||||
|
subject.Set(key, val);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ACTION: REMOVE
|
||||||
|
if (reference.ContainsKey(key))
|
||||||
|
{
|
||||||
|
if (showOps)Console.WriteLine($"remove ${key}");
|
||||||
|
reference.Remove(key);
|
||||||
|
subject.Remove(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try removing non-existent key (should be safe)
|
||||||
|
subject.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. VERIFY CONSISTENCY (Expensive but necessary)
|
||||||
|
// We check consistency every 1000 ops or if the tree is small,
|
||||||
|
// to keep the test fast enough.
|
||||||
|
//if (i % 1000 == 0 || reference.Count < 100)
|
||||||
|
//{
|
||||||
|
AssertConsistency(reference, subject);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final check
|
||||||
|
AssertConsistency(reference, subject);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_output.WriteLine($"FAILED at iteration with SEED: {Seed}");
|
||||||
|
throw; // Re-throw to fail the test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Fuzz_Range_Queries()
|
||||||
|
{
|
||||||
|
// Validates that your Range Enumerator matches LINQ on the reference
|
||||||
|
const int Iterations = 1000;
|
||||||
|
const int KeyRange = 2000;
|
||||||
|
int Seed = Environment.TickCount;
|
||||||
|
|
||||||
|
var reference = new SortedDictionary<int, int>();
|
||||||
|
var subject = BaseOrderedMap<int, int, StandardStrategy<int>>.CreateTransient(_strategy);
|
||||||
|
var random = new Random(Seed);
|
||||||
|
|
||||||
|
// Fill Data
|
||||||
|
for(int i=0; i<KeyRange; i++)
|
||||||
|
{
|
||||||
|
int k = random.Next(KeyRange);
|
||||||
|
reference[k] = k;
|
||||||
|
subject.Set(k, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
var persistent = subject.ToPersistent();
|
||||||
|
|
||||||
|
for (int i = 0; i < Iterations; i++)
|
||||||
|
{
|
||||||
|
int min = random.Next(KeyRange);
|
||||||
|
int max = min + random.Next(KeyRange - min); // Ensure max >= min
|
||||||
|
|
||||||
|
// 1. Reference Result (LINQ)
|
||||||
|
// Note: SortedDictionary doesn't have a direct Range query, so we filter memory.
|
||||||
|
var expected = reference
|
||||||
|
.Where(kv => kv.Key >= min && kv.Key <= max)
|
||||||
|
.Select(kv => kv.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 2. Subject Result
|
||||||
|
var actual = persistent.Range(min, max)
|
||||||
|
.Select(kv => kv.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 3. Compare
|
||||||
|
if (!expected.SequenceEqual(actual))
|
||||||
|
{
|
||||||
|
_output.WriteLine($"Range Mismatch! Range: [{min}, {max}]");
|
||||||
|
_output.WriteLine($"Expected: {string.Join(",", expected)}");
|
||||||
|
_output.WriteLine($"Actual: {string.Join(",", actual)}");
|
||||||
|
Assert.Fail("Range query results differ.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertConsistency(SortedDictionary<int, int> expected, TransientMap<int, int, StandardStrategy<int>> actual)
|
||||||
|
{
|
||||||
|
// 1. Count
|
||||||
|
if (expected.Count != actual.Count)
|
||||||
|
{
|
||||||
|
Console.WriteLine("BP");
|
||||||
|
throw new Exception($"Count Mismatch! Expected {expected.Count}, Got {actual.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Full Scan Verification
|
||||||
|
using var enumerator = actual.GetEnumerator();
|
||||||
|
foreach (var kvp in expected)
|
||||||
|
{
|
||||||
|
if (!enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
throw new Exception("Enumerator ended too early!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumerator.Current.Key != kvp.Key || enumerator.Current.Value != kvp.Value)
|
||||||
|
{
|
||||||
|
Console.WriteLine("BP");
|
||||||
|
throw new Exception($"Content Mismatch! Expected [{kvp.Key}:{kvp.Value}], Got [{enumerator.Current.Key}:{enumerator.Current.Value}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
throw new Exception("Enumerator has extra items!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using BenchmarkDotNet.Running;
|
|
||||||
using PersistentMap;
|
|
||||||
|
|
||||||
// Ensure your PersistentMap namespace is included here
|
|
||||||
// using MyProject.Collections;
|
|
||||||
|
|
||||||
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class ImmutableBenchmark
|
|
||||||
{
|
|
||||||
[Params(10, 100)]
|
|
||||||
public int N { get; set; }
|
|
||||||
|
|
||||||
[Params(10000)]
|
|
||||||
public int CollectionSize { get; set; }
|
|
||||||
|
|
||||||
private ImmutableDictionary<string, string> _immutableDict;
|
|
||||||
private ImmutableSortedDictionary<string, string> _immutableSortedDict;
|
|
||||||
|
|
||||||
// 1. Add field for your map
|
|
||||||
private PersistentMap<string, string, UnicodeStrategy> _persistentMap;
|
|
||||||
|
|
||||||
private string[] _searchKeys;
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
var random = new Random(42);
|
|
||||||
var data = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
while (data.Count < CollectionSize)
|
|
||||||
{
|
|
||||||
string key = GenerateRandomString(random, N);
|
|
||||||
if (!data.ContainsKey(key))
|
|
||||||
{
|
|
||||||
data[key] = "value";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_immutableDict = data.ToImmutableDictionary();
|
|
||||||
_immutableSortedDict = data.ToImmutableSortedDictionary();
|
|
||||||
|
|
||||||
// 2. Initialize your map.
|
|
||||||
// ASSUMPTION: Standard immutable pattern (Add returns new instance).
|
|
||||||
// Adjust if you have a bulk loader like .ToPersistentMap() or a constructor.
|
|
||||||
_persistentMap = PersistentMap<string, string, UnicodeStrategy>.Empty(new UnicodeStrategy());
|
|
||||||
foreach (var kvp in data)
|
|
||||||
{
|
|
||||||
_persistentMap = _persistentMap.Set(kvp.Key, kvp.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
_searchKeys = data.Keys.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public string ImmutableDict_Lookup()
|
|
||||||
{
|
|
||||||
var key = _searchKeys[CollectionSize / 2];
|
|
||||||
_immutableDict.TryGetValue(key, out var value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public string ImmutableSortedDict_Lookup()
|
|
||||||
{
|
|
||||||
var key = _searchKeys[CollectionSize / 2];
|
|
||||||
_immutableSortedDict.TryGetValue(key, out var value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Add the benchmark case
|
|
||||||
[Benchmark]
|
|
||||||
public string PersistentMap_Lookup()
|
|
||||||
{
|
|
||||||
var key = _searchKeys[CollectionSize / 2];
|
|
||||||
// Adjust API call if your map uses a different method (e.g. Find, Get, indexer)
|
|
||||||
_persistentMap.TryGetValue(key, out var value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
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<string, int, UnicodeStrategy> _persistentMap;
|
|
||||||
private ImmutableSortedDictionary<string, int> _sysSorted;
|
|
||||||
private LanguageExt.HashMap<string, int> _langExtHash;
|
|
||||||
private LanguageExt.Map<string, int> _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<string, int>();
|
|
||||||
|
|
||||||
// 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<string, int, UnicodeStrategy>.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<T> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using BenchmarkDotNet.Running;
|
|
||||||
using LanguageExt; // Your Namespace
|
|
||||||
using LanguageExt;
|
|
||||||
using LanguageExt;
|
|
||||||
using PersistentMap; // NuGet: LanguageExt.Core
|
|
||||||
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class ImmutableCollectionBenchmarks
|
|
||||||
{
|
|
||||||
[Params(100, 1000, 100_000)]
|
|
||||||
public int N;
|
|
||||||
|
|
||||||
private int[] _keys;
|
|
||||||
private int[] _values;
|
|
||||||
|
|
||||||
// --- 1. Your Collections ---
|
|
||||||
private PersistentMap<int, int, IntStrategy> _niceMap;
|
|
||||||
private IntStrategy _strategy;
|
|
||||||
|
|
||||||
// --- 2. Microsoft Collections ---
|
|
||||||
private System.Collections.Immutable.ImmutableSortedDictionary<int, int> _msSortedMap;
|
|
||||||
private System.Collections.Immutable.ImmutableDictionary<int, int> _msHashMap;
|
|
||||||
|
|
||||||
// --- 3. LanguageExt Collections ---
|
|
||||||
private LanguageExt.Map<int, int> _leMap; // AVL Tree (Sorted)
|
|
||||||
private LanguageExt.HashMap<int, int> _leHashMap; // Hash Array Mapped Trie (Unsorted)
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
// Generate Data
|
|
||||||
var rand = new Random(42);
|
|
||||||
_keys = Enumerable.Range(0, N).Select(x => x * 2).ToArray();
|
|
||||||
rand.Shuffle(_keys);
|
|
||||||
_values = _keys.Select(k => k * 100).ToArray();
|
|
||||||
_strategy = new IntStrategy();
|
|
||||||
|
|
||||||
// 1. Setup NiceBTree
|
|
||||||
var transient = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
|
||||||
for (int i = 0; i < N; i++) transient.Set(_keys[i], _values[i]);
|
|
||||||
_niceMap = transient.ToPersistent();
|
|
||||||
|
|
||||||
// 2. Setup MS Immutable
|
|
||||||
var msBuilder = System.Collections.Immutable.ImmutableSortedDictionary.CreateBuilder<int, int>();
|
|
||||||
for (int i = 0; i < N; i++) msBuilder.Add(_keys[i], _values[i]);
|
|
||||||
_msSortedMap = msBuilder.ToImmutable();
|
|
||||||
|
|
||||||
_msHashMap = System.Collections.Immutable.ImmutableDictionary.CreateRange(
|
|
||||||
_keys.Zip(_values, (k, v) => new System.Collections.Generic.KeyValuePair<int, int>(k, v)));
|
|
||||||
|
|
||||||
// 3. Setup LanguageExt
|
|
||||||
// Note: LanguageExt performs best when bulk-loaded from tuples
|
|
||||||
var tuples = _keys.Zip(_values, (k, v) => (k, v));
|
|
||||||
_leMap = new LanguageExt.Map<int,int>(tuples);
|
|
||||||
_leHashMap = new HashMap<int, int>(tuples);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================
|
|
||||||
// 1. BUILD (Item by Item)
|
|
||||||
// Note: LanguageExt has no "Mutable Builder", so this tests
|
|
||||||
// the cost of pure immutable inserts vs your Transient/Builder.
|
|
||||||
// =========================================================
|
|
||||||
|
|
||||||
[Benchmark(Description = "Build: PersistentMap (Transient)")]
|
|
||||||
public int Build_NiceBTree()
|
|
||||||
{
|
|
||||||
var t = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
|
||||||
for (int i = 0; i < N; i++) t.Set(_keys[i], _values[i]);
|
|
||||||
return t.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Build: PersistentMap (Persistent)")]
|
|
||||||
public int Build_PersistentBTree()
|
|
||||||
{
|
|
||||||
var t = PersistentMap<int, int, IntStrategy>.Empty(_strategy);
|
|
||||||
for (int i = 0; i < N; i++) t.Set(_keys[i], _values[i]);
|
|
||||||
return t.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Build: MS Sorted (Builder)")]
|
|
||||||
public int Build_MsSorted()
|
|
||||||
{
|
|
||||||
var b = System.Collections.Immutable.ImmutableSortedDictionary.CreateBuilder<int, int>();
|
|
||||||
for (int i = 0; i < N; i++) b.Add(_keys[i], _values[i]);
|
|
||||||
return b.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Build: LanguageExt Map (AVL)")]
|
|
||||||
public int Build_LanguageExt_Map()
|
|
||||||
{
|
|
||||||
var map = LanguageExt.Map<int, int>.Empty;
|
|
||||||
for (int i = 0; i < N; i++)
|
|
||||||
{
|
|
||||||
// Pure immutable add
|
|
||||||
map = map.Add(_keys[i], _values[i]);
|
|
||||||
}
|
|
||||||
return map.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Build: LanguageExt HashMap")]
|
|
||||||
public int Build_LanguageExt_HashMap()
|
|
||||||
{
|
|
||||||
var map = LanguageExt.HashMap<int, int>.Empty;
|
|
||||||
for (int i = 0; i < N; i++)
|
|
||||||
{
|
|
||||||
map = map.Add(_keys[i], _values[i]);
|
|
||||||
}
|
|
||||||
return map.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================
|
|
||||||
// 2. READ (Lookup)
|
|
||||||
// =========================================================
|
|
||||||
|
|
||||||
[Benchmark(Description = "Read: NiceBTree")]
|
|
||||||
public int Read_NiceBTree()
|
|
||||||
{
|
|
||||||
var found = 1;
|
|
||||||
if (_niceMap.TryGetValue(_keys[N/2], out _)) found++;
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Read: MS Sorted")]
|
|
||||||
public int Read_MsSorted()
|
|
||||||
{
|
|
||||||
int found = 0;
|
|
||||||
if (_msSortedMap.ContainsKey(_keys[N/2])) found++;
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Read: LanguageExt Map")]
|
|
||||||
public int Read_LanguageExt_Map()
|
|
||||||
{
|
|
||||||
int found = 0;
|
|
||||||
// Find returns Option<V>, IsSome checks if it exists
|
|
||||||
if (_leMap.Find(_keys[N/2]).IsSome) found++;
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Read: LanguageExt HashMap")]
|
|
||||||
public int Read_LanguageExt_HashMap()
|
|
||||||
{
|
|
||||||
int found = 0;
|
|
||||||
|
|
||||||
if (_leHashMap.Find(_keys[N/2]).IsSome) found++;
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================
|
|
||||||
// 3. ITERATE (Foreach)
|
|
||||||
// =========================================================
|
|
||||||
|
|
||||||
[Benchmark(Description = "Iterate: NiceBTree")]
|
|
||||||
public int Iterate_NiceBTree()
|
|
||||||
{
|
|
||||||
int sum = 0;
|
|
||||||
foreach (var kvp in _niceMap) sum += kvp.Key;
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Iterate: MS Sorted")]
|
|
||||||
public int Iterate_MsSorted()
|
|
||||||
{
|
|
||||||
int sum = 0;
|
|
||||||
foreach (var kvp in _msSortedMap) sum += kvp.Key;
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Iterate: LanguageExt Map")]
|
|
||||||
public int Iterate_LanguageExt_Map()
|
|
||||||
{
|
|
||||||
int sum = 0;
|
|
||||||
// LanguageExt Map is IEnumerable<(Key, Value)>
|
|
||||||
foreach (var item in _leMap) sum += item.Key;
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Iterate: LanguageExt HashMap")]
|
|
||||||
public int Iterate_LanguageExt_HashMap()
|
|
||||||
{
|
|
||||||
int sum = 0;
|
|
||||||
foreach (var item in _leHashMap) sum += item.Key;
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================
|
|
||||||
// 4. SET (Persistent / Immutable Update)
|
|
||||||
// =========================================================
|
|
||||||
|
|
||||||
[Benchmark(Description = "Set: NiceBTree")]
|
|
||||||
public PersistentMap<int, int, IntStrategy> Set_NiceBTree()
|
|
||||||
{
|
|
||||||
return _niceMap.Set(_keys[N / 2], -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Set: MS Sorted")]
|
|
||||||
public System.Collections.Immutable.ImmutableSortedDictionary<int, int> Set_MsSorted()
|
|
||||||
{
|
|
||||||
return _msSortedMap.SetItem(_keys[N / 2], -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Set: LanguageExt Map")]
|
|
||||||
public LanguageExt.Map<int, int> Set_LanguageExt_Map()
|
|
||||||
{
|
|
||||||
return _leMap.SetItem(_keys[N / 2], -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Set: LanguageExt HashMap")]
|
|
||||||
public LanguageExt.HashMap<int, int> Set_LanguageExt_HashMap()
|
|
||||||
{
|
|
||||||
return _leHashMap.SetItem(_keys[N / 2], -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
benchmarks/MyBenchMarks/MyBenchMarks.csproj
Normal file
19
benchmarks/MyBenchMarks/MyBenchMarks.csproj
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||||
|
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\PersistentMap\PersistentMap.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
368
benchmarks/MyBenchMarks/Program.cs
Normal file
368
benchmarks/MyBenchMarks/Program.cs
Normal file
|
|
@ -0,0 +1,368 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Running;
|
||||||
|
using LanguageExt;
|
||||||
|
using PersistentMap;
|
||||||
|
|
||||||
|
namespace MapBenchmarks;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class IntMapBenchmarks
|
||||||
|
{
|
||||||
|
[Params(100, 1000, 10000, 100000)]
|
||||||
|
public int N { get; set; }
|
||||||
|
|
||||||
|
private int[] _allKeys;
|
||||||
|
private int[] _retrieveKeys;
|
||||||
|
private int[] _updateKeys;
|
||||||
|
private int[] _removeKeys;
|
||||||
|
private int[] _mixedKeys; // Half existing, half new
|
||||||
|
|
||||||
|
// Pre-built collections for read/update/remove tests
|
||||||
|
private ImmutableDictionary<int, int> _immDict;
|
||||||
|
private ImmutableSortedDictionary<int, int> _immSortedDict;
|
||||||
|
private LanguageExt.Map<int, int> _extMap;
|
||||||
|
private LanguageExt.HashMap<int, int> _extHashMap;
|
||||||
|
private PersistentMap<int, int, IntStrategy> _persistentMap;
|
||||||
|
|
||||||
|
private readonly IntStrategy _intStrategy = new IntStrategy();
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var rnd = new Random(42);
|
||||||
|
|
||||||
|
// Build integer keys (inserted sorted)
|
||||||
|
_allKeys = Enumerable.Range(0, N).ToArray();
|
||||||
|
|
||||||
|
int subsetSize = Math.Max(1, N / 10);
|
||||||
|
|
||||||
|
// Subsets for different operations
|
||||||
|
var shuffled = _allKeys.OrderBy(x => rnd.Next()).ToArray();
|
||||||
|
_retrieveKeys = shuffled.Take(subsetSize).ToArray();
|
||||||
|
_updateKeys = shuffled.Skip(subsetSize).Take(subsetSize).ToArray();
|
||||||
|
_removeKeys = shuffled.Skip(subsetSize * 2).Take(subsetSize).ToArray();
|
||||||
|
|
||||||
|
// Mixed keys: half existing, half completely new (N + 1 to N + subsetSize/2)
|
||||||
|
var existingHalf = shuffled.Skip(subsetSize * 3).Take(subsetSize / 2).ToArray();
|
||||||
|
var newHalf = Enumerable.Range(N + 1, subsetSize - (subsetSize / 2)).ToArray();
|
||||||
|
_mixedKeys = existingHalf.Concat(newHalf).OrderBy(x => rnd.Next()).ToArray();
|
||||||
|
|
||||||
|
// Pre-build collections
|
||||||
|
_immDict = ImmutableDictionary.CreateRange(_allKeys.Select(k => new KeyValuePair<int, int>(k, k)));
|
||||||
|
_immSortedDict = ImmutableSortedDictionary.CreateRange(_allKeys.Select(k => new KeyValuePair<int, int>(k, k)));
|
||||||
|
|
||||||
|
_extMap = LanguageExt.Map.empty<int, int>();
|
||||||
|
_extHashMap = LanguageExt.HashMap.empty<int, int>();
|
||||||
|
foreach (var k in _allKeys)
|
||||||
|
{
|
||||||
|
_extMap = _extMap.AddOrUpdate(k, k);
|
||||||
|
_extHashMap = _extHashMap.AddOrUpdate(k, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
var transient = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_intStrategy);
|
||||||
|
foreach (var k in _allKeys) transient.Set(k, k);
|
||||||
|
_persistentMap = transient.ToPersistent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 1. BUILD ---
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableDictionary<int, int> Build_ImmDict()
|
||||||
|
{
|
||||||
|
var map = ImmutableDictionary<int, int>.Empty;
|
||||||
|
foreach (var k in _allKeys) map = map.Add(k, k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableSortedDictionary<int, int> Build_ImmSortedDict()
|
||||||
|
{
|
||||||
|
var map = ImmutableSortedDictionary<int, int>.Empty;
|
||||||
|
foreach (var k in _allKeys) map = map.Add(k, k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.Map<int, int> Build_ExtMap()
|
||||||
|
{
|
||||||
|
var map = LanguageExt.Map.empty<int, int>();
|
||||||
|
foreach (var k in _allKeys) map = map.AddOrUpdate(k, k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.HashMap<int, int> Build_ExtHashMap()
|
||||||
|
{
|
||||||
|
var map = LanguageExt.HashMap.empty<int, int>();
|
||||||
|
foreach (var k in _allKeys) map = map.AddOrUpdate(k, k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> Build_PersistentMap()
|
||||||
|
{
|
||||||
|
var map = PersistentMap<int, int, IntStrategy>.Empty(_intStrategy);
|
||||||
|
foreach (var k in _allKeys) map = map.Set(k, k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> Build_TransientMap()
|
||||||
|
{
|
||||||
|
var map = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_intStrategy);
|
||||||
|
foreach (var k in _allKeys) map.Set(k, k);
|
||||||
|
return map.ToPersistent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. RETRIEVAL ---
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Retrieve_ImmDict()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (var k in _retrieveKeys)
|
||||||
|
if (_immDict.TryGetValue(k, out _)) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Retrieve_ImmSortedDict()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (var k in _retrieveKeys)
|
||||||
|
if (_immSortedDict.TryGetValue(k, out _)) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Retrieve_ExtMap()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (var k in _retrieveKeys)
|
||||||
|
if (_extMap.Find(k).IsSome) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Retrieve_ExtHashMap()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (var k in _retrieveKeys)
|
||||||
|
if (_extHashMap.Find(k).IsSome) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Retrieve_PersistentMap()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (var k in _retrieveKeys)
|
||||||
|
if (_persistentMap.TryGetValue(k, out _)) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. UPDATING ---
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableDictionary<int, int> Update_ImmDict()
|
||||||
|
{
|
||||||
|
var map = _immDict;
|
||||||
|
foreach (var k in _updateKeys) map = map.SetItem(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> Update_PersistentMap()
|
||||||
|
{
|
||||||
|
var map = _persistentMap;
|
||||||
|
foreach (var k in _updateKeys) map = map.Set(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> Update_TransientMap()
|
||||||
|
{
|
||||||
|
var transient = _persistentMap.ToTransient();
|
||||||
|
foreach (var k in _updateKeys) transient.Set(k, 999);
|
||||||
|
return transient.ToPersistent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableSortedDictionary<int, int> Update_ImmSortedDict()
|
||||||
|
{
|
||||||
|
var map = _immSortedDict;
|
||||||
|
foreach (var k in _updateKeys) map = map.SetItem(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.Map<int, int> Update_ExtMap()
|
||||||
|
{
|
||||||
|
var map = _extMap;
|
||||||
|
foreach (var k in _updateKeys) map = map.SetItem(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.HashMap<int, int> Update_ExtHashMap()
|
||||||
|
{
|
||||||
|
var map = _extHashMap;
|
||||||
|
foreach (var k in _updateKeys) map = map.SetItem(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. UPDATE & SET (MIXED) ---
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableDictionary<int, int> UpdateSet_ImmDict()
|
||||||
|
{
|
||||||
|
var map = _immDict;
|
||||||
|
foreach (var k in _mixedKeys) map = map.SetItem(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> UpdateSet_PersistentMap()
|
||||||
|
{
|
||||||
|
var map = _persistentMap;
|
||||||
|
foreach (var k in _mixedKeys) map = map.Set(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> UpdateSet_TransientMap()
|
||||||
|
{
|
||||||
|
var transient = _persistentMap.ToTransient();
|
||||||
|
foreach (var k in _mixedKeys) transient.Set(k, 999);
|
||||||
|
return transient.ToPersistent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableSortedDictionary<int, int> UpdateSet_ImmSortedDict()
|
||||||
|
{
|
||||||
|
var map = _immSortedDict;
|
||||||
|
foreach (var k in _mixedKeys) map = map.SetItem(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.Map<int, int> UpdateSet_ExtMap()
|
||||||
|
{
|
||||||
|
var map = _extMap;
|
||||||
|
foreach (var k in _mixedKeys) map = map.AddOrUpdate(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.HashMap<int, int> UpdateSet_ExtHashMap()
|
||||||
|
{
|
||||||
|
var map = _extHashMap;
|
||||||
|
foreach (var k in _mixedKeys) map = map.AddOrUpdate(k, 999);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 5. ITERATION ---
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Iterate_ImmDict()
|
||||||
|
{
|
||||||
|
int sum = 0;
|
||||||
|
foreach (var kvp in _immDict) sum += kvp.Value;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Iterate_PersistentMap()
|
||||||
|
{
|
||||||
|
int sum = 0;
|
||||||
|
foreach (var kvp in _persistentMap) sum += kvp.Value;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Iterate_ImmSortedDict()
|
||||||
|
{
|
||||||
|
int sum = 0;
|
||||||
|
foreach (var kvp in _immSortedDict) sum += kvp.Value;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Iterate_ExtMap()
|
||||||
|
{
|
||||||
|
int sum = 0;
|
||||||
|
foreach (var kvp in _extMap) sum += kvp.Value;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Iterate_ExtHashMap()
|
||||||
|
{
|
||||||
|
int sum = 0;
|
||||||
|
foreach (var kvp in _extHashMap) sum += kvp.Value;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- 6. REMOVAL ---
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableDictionary<int, int> Remove_ImmDict()
|
||||||
|
{
|
||||||
|
var map = _immDict;
|
||||||
|
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> Remove_PersistentMap()
|
||||||
|
{
|
||||||
|
var map = _persistentMap;
|
||||||
|
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
[Benchmark]
|
||||||
|
public PersistentMap<int, int, IntStrategy> Remove_TransientMap()
|
||||||
|
{
|
||||||
|
var transient = _persistentMap.ToTransient();
|
||||||
|
foreach (var k in _removeKeys) transient.Remove(k);
|
||||||
|
return transient.ToPersistent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ImmutableSortedDictionary<int, int> Remove_ImmSortedDict()
|
||||||
|
{
|
||||||
|
var map = _immSortedDict;
|
||||||
|
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.Map<int, int> Remove_ExtMap()
|
||||||
|
{
|
||||||
|
var map = _extMap;
|
||||||
|
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public LanguageExt.HashMap<int, int> Remove_ExtHashMap()
|
||||||
|
{
|
||||||
|
var map = _extHashMap;
|
||||||
|
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
BenchmarkSwitcher
|
||||||
|
.FromAssembly(typeof(IntMapBenchmarks).Assembly)
|
||||||
|
.Run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue