Compare commits
No commits in common. "9242c1c751e586f25508d83724335442f3850820" and "ae3e8ae8c4837e362f32d1e381b5486bf2e20ad7" have entirely different histories.
9242c1c751
...
ae3e8ae8c4
13 changed files with 669 additions and 1446 deletions
|
|
@ -12,8 +12,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstImmutableDict", "ben
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstLanguageExt", "benchmarks\AgainstLanguageExt\AgainstLanguageExt.csproj", "{6C16526B-5139-4EA3-BF74-E6320F467198}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyBenchMarks", "benchmarks\MyBenchMarks\MyBenchMarks.csproj", "{769E1CEA-7E01-405B-80A2-95CBF432A2BA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -23,7 +21,6 @@ Global
|
|||
{CA49AA3C-0CE6-4735-887F-FB3631D63CEE} = {B0432C7A-80E2-4EA6-8FAB-B8F23A8C39DE}
|
||||
{13304F19-7ED3-4C40-9A08-46D539667D50} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
||||
{6C16526B-5139-4EA3-BF74-E6320F467198} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CA49AA3C-0CE6-4735-887F-FB3631D63CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -42,9 +39,5 @@ Global
|
|||
{6C16526B-5139-4EA3-BF74-E6320F467198}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace PersistentMap
|
|||
public static bool TryGetValue<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out V value)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
// 1. Calculate ONCE
|
||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||
|
||||
Node<K> current = root;
|
||||
|
|
@ -21,6 +22,7 @@ namespace PersistentMap
|
|||
if (current.IsLeaf)
|
||||
{
|
||||
var leaf = current.AsLeaf<V>();
|
||||
// Leaf uses standard FindIndex (Lower Bound) to find exact match
|
||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
|
||||
{
|
||||
|
|
@ -32,6 +34,7 @@ namespace PersistentMap
|
|||
}
|
||||
else
|
||||
{
|
||||
// FIX: Internal uses FindRoutingIndex (Upper Bound)
|
||||
var internalNode = current.AsInternal();
|
||||
int index = FindRoutingIndex(internalNode, key, keyPrefix, strategy);
|
||||
current = internalNode.Children[index]!;
|
||||
|
|
@ -39,6 +42,7 @@ namespace PersistentMap
|
|||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
public static Node<K> Set<K, V>(Node<K> root, K key, V value, IKeyStrategy<K> strategy, OwnerId owner, out bool countChanged)
|
||||
{
|
||||
root = root.EnsureEditable(owner);
|
||||
|
|
@ -47,19 +51,13 @@ namespace PersistentMap
|
|||
|
||||
if (splitResult != null)
|
||||
{
|
||||
var newRoot = strategy.UsesPrefixes
|
||||
? new PrefixInternalNode<K>(owner)
|
||||
: new InternalNode<K>(owner);
|
||||
|
||||
newRoot.Keys[0] = splitResult.Separator;
|
||||
var newRoot = new InternalNode<K>(owner);
|
||||
newRoot.Children[0] = root;
|
||||
newRoot.Keys[0] = splitResult.Separator;
|
||||
newRoot.Children[1] = splitResult.NewNode;
|
||||
newRoot.SetCount(1);
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
newRoot.AllPrefixes[0] = strategy.GetPrefix(splitResult.Separator);
|
||||
}
|
||||
|
||||
return newRoot;
|
||||
}
|
||||
|
|
@ -67,8 +65,10 @@ namespace PersistentMap
|
|||
return root;
|
||||
}
|
||||
|
||||
// Recursive Helper
|
||||
private static SplitResult<K>? InsertRecursive<K, V>(Node<K> node, K key, V value, IKeyStrategy<K> strategy, OwnerId owner, out bool added)
|
||||
{
|
||||
// 1. Calculate ONCE
|
||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||
|
||||
if (node.IsLeaf)
|
||||
|
|
@ -79,11 +79,11 @@ namespace PersistentMap
|
|||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
|
||||
{
|
||||
leaf.Values[index] = value;
|
||||
added = false;
|
||||
added = false; // Key existed, value updated. Count does not change.
|
||||
return null;
|
||||
}
|
||||
|
||||
added = true;
|
||||
added = true; // New key. Count +1.
|
||||
if (leaf.Header.Count < LeafNode<K, V>.Capacity)
|
||||
{
|
||||
InsertIntoLeaf(leaf, index, key, value, strategy);
|
||||
|
|
@ -120,6 +120,7 @@ namespace PersistentMap
|
|||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
public static Node<K> Remove<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, OwnerId owner, out bool countChanged)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
|
|
@ -142,9 +143,11 @@ namespace PersistentMap
|
|||
return root;
|
||||
}
|
||||
|
||||
// Recursive Helper
|
||||
private static bool RemoveRecursive<K, V, TStrategy>(Node<K> node, K key, TStrategy strategy, OwnerId owner, out bool removed)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
// 1. Calculate ONCE
|
||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||
|
||||
if (node.IsLeaf)
|
||||
|
|
@ -155,11 +158,11 @@ namespace PersistentMap
|
|||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
|
||||
{
|
||||
RemoveFromLeaf(leaf, index, strategy);
|
||||
removed = true;
|
||||
return leaf.Header.Count < LeafNode<K, V>.MergeThreshold;
|
||||
removed = true; // Item removed. Count -1.
|
||||
return leaf.Header.Count <LeafNode<K, V>.MergeThreshold;
|
||||
}
|
||||
|
||||
removed = false;
|
||||
removed = false; // Item not found.
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
|
@ -184,127 +187,99 @@ namespace PersistentMap
|
|||
// Internal Helpers: Search
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// Used by Leaf Nodes: Finds the first key >= searchKey (Lower Bound)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static int FindIndex<K, TStrategy>(Node<K> node, K key, long keyPrefix, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
if (typeof(K) == typeof(int))
|
||||
{
|
||||
Span<K> keys = node.GetKeys();
|
||||
ref K firstKeyRef = ref MemoryMarshal.GetReference(keys);
|
||||
ref int firstIntRef = ref Unsafe.As<K, int>(ref firstKeyRef);
|
||||
ReadOnlySpan<int> intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length);
|
||||
int intKey = Unsafe.As<K, int>(ref key);
|
||||
return IntScanner.FindFirstGreaterOrEqual(intKeys, intKey);
|
||||
}
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
// Use the pre-calculated prefix here!
|
||||
int index = PrefixScanner.FindFirstGreaterOrEqual(node.Prefixes, keyPrefix);
|
||||
return RefineSearch(index, node.GetKeys(), key, strategy);
|
||||
}
|
||||
|
||||
return FallbackSearchKeys(node.GetKeys(), key, strategy);
|
||||
return LinearSearchKeys(node.GetKeys(), key, strategy);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearSearchKeys<K, TStrategy>(Span<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
int i = 0;
|
||||
// Standard linear scan on the keys array
|
||||
while (i < keys.Length && strategy.Compare(keys[i], key) < 0)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int RefineSearch<K, TStrategy>(int startIndex, Span<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
int i = startIndex;
|
||||
// JIT can now inline 'strategy.Compare' here!
|
||||
while (i < keys.Length && strategy.Compare(keys[i], key) < 0)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
// Used by Internal Nodes: Finds the child index to descend into.
|
||||
// If Key == Separator, we must go RIGHT (index + 1), so we need (Upper Bound).
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static int FindRoutingIndex<K, TStrategy>(InternalNode<K> node, K key, long keyPrefix, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
if (!strategy.UsesPrefixes)
|
||||
{
|
||||
return FallbackRoutingKeys(node.GetKeys(), key, strategy);
|
||||
return LinearSearchRouting(node.GetKeys(), key, strategy);
|
||||
}
|
||||
|
||||
// Use the pre-calculated prefix here!
|
||||
int index = PrefixScanner.FindFirstGreaterOrEqual(node.Prefixes, keyPrefix);
|
||||
return RefineRouting(index, node.GetKeys(), key, strategy);
|
||||
return RefineRouting(index, node.Keys, node.Header.Count, key, strategy);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int RefineSearch<K, TStrategy>(int startIndex, ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
int i = startIndex;
|
||||
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int RefineRouting<K, TStrategy>(int startIndex, ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
int i = startIndex;
|
||||
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int FallbackSearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
return strategy.UseBinarySearch
|
||||
? BinarySearchKeys(keys, key, strategy)
|
||||
: LinearSearchKeys(keys, key, strategy);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int FallbackRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
return strategy.UseBinarySearch
|
||||
? BinaryRoutingKeys(keys, key, strategy)
|
||||
: LinearRoutingKeys(keys, key, strategy);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearSearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
private static int LinearSearchRouting<K, TStrategy>(Span<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
int i = 0;
|
||||
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
|
||||
// Routing: Skip everything that is LessOrEqual.
|
||||
// We stop at the first item that is Greater.
|
||||
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
// Overload for primitive types (avoids IComparer call overhead in fallback)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
private static int LinearSearchRouting<T>(Span<T> keys, T key) where T : struct, IComparable<T>
|
||||
{
|
||||
int i = 0;
|
||||
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
|
||||
while (i < keys.Length && keys[i].CompareTo(key) <= 0) i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int BinarySearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
private static int RefineRouting<K, TStrategy>(int startIndex, K[] keys, int count, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
int low = 0;
|
||||
int high = keys.Length - 1;
|
||||
while (low <= high)
|
||||
int i = startIndex;
|
||||
// DIFFERENCE: We continue past valid matches.
|
||||
// We want the first key STRICTLY GREATER than target.
|
||||
// If keys[i] == key, we increment (go to right child).
|
||||
while (i < count && strategy.Compare(keys[i], key) <= 0)
|
||||
{
|
||||
int mid = low + ((high - low) >> 1);
|
||||
int cmp = strategy.Compare(keys[mid], key);
|
||||
if (cmp == 0) return mid;
|
||||
if (cmp < 0) low = mid + 1;
|
||||
else high = mid - 1;
|
||||
i++;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int BinaryRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
int low = 0;
|
||||
int high = keys.Length - 1;
|
||||
while (low <= high)
|
||||
{
|
||||
int mid = low + ((high - low) >> 1);
|
||||
int cmp = strategy.Compare(keys[mid], key);
|
||||
if (cmp <= 0) low = mid + 1;
|
||||
else high = mid - 1;
|
||||
}
|
||||
return low;
|
||||
return i;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
|
|
@ -315,13 +290,68 @@ namespace PersistentMap
|
|||
{
|
||||
public Node<K> NewNode;
|
||||
public K Separator;
|
||||
public SplitResult(Node<K> newNode, K separator)
|
||||
public SplitResult(Node<K> newNode, K separator) { NewNode = newNode; Separator = separator; }
|
||||
}
|
||||
|
||||
private static SplitResult<K>? InsertRecur2sive<K, V, TStrategy>(Node<K> node, K key, V value, TStrategy strategy, OwnerId owner)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
NewNode = newNode;
|
||||
Separator = separator;
|
||||
|
||||
// 1. Calculate ONCE
|
||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||
|
||||
|
||||
// --- LEAF CASE ---
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
var leaf = node.AsLeaf<V>();
|
||||
// Leaf uses FindIndex
|
||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||
|
||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
|
||||
{
|
||||
leaf.Values[index] = value;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (leaf.Header.Count < LeafNode<K, V>.Capacity)
|
||||
{
|
||||
InsertIntoLeaf(leaf, index, key, value, strategy);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SplitLeaf(leaf, index, key, value, strategy, owner);
|
||||
}
|
||||
}
|
||||
|
||||
// --- INTERNAL CASE ---
|
||||
var internalNode = node.AsInternal();
|
||||
|
||||
// FIX: Internal uses FindRoutingIndex
|
||||
int childIndex = FindRoutingIndex(internalNode, key, keyPrefix, strategy);
|
||||
|
||||
var child = internalNode.Children[childIndex]!.EnsureEditable(owner);
|
||||
internalNode.Children[childIndex] = child;
|
||||
|
||||
var split = InsertRecursive(child, key, value, strategy, owner, out _);
|
||||
|
||||
if (split != null)
|
||||
{
|
||||
// ... checks ...
|
||||
// Use childIndex here
|
||||
if (internalNode.Header.Count < InternalNode<K>.Capacity - 1)
|
||||
{
|
||||
InsertIntoInternal(internalNode, childIndex, split.Separator, split.NewNode, strategy);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SplitInternal(internalNode, childIndex, split.Separator, split.NewNode, strategy, owner);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static void InsertIntoLeaf<K, V, TStrategy>(LeafNode<K, V> leaf, int index, K key, V value, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
|
|
@ -329,58 +359,66 @@ namespace PersistentMap
|
|||
if (index < count)
|
||||
{
|
||||
int moveCount = count - index;
|
||||
// Fast Span memory moves
|
||||
leaf.Keys.AsSpan(index, moveCount).CopyTo(leaf.Keys.AsSpan(index + 1));
|
||||
leaf.Values.AsSpan(index, moveCount).CopyTo(leaf.Values.AsSpan(index + 1));
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
leaf.AllPrefixes.Slice(index, moveCount).CopyTo(leaf.AllPrefixes.Slice(index + 1));
|
||||
leaf.AllPrefixes.Slice(index, count - index)
|
||||
.CopyTo(leaf.AllPrefixes.Slice(index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
leaf.Keys[index] = key;
|
||||
leaf.Values[index] = value;
|
||||
|
||||
// This fails if leaf.Values is a Span<V> of length 'count'
|
||||
leaf.Values[index] = value;
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
leaf.AllPrefixes[index] = strategy.GetPrefix(key);
|
||||
}
|
||||
leaf.AllPrefixes![index] = strategy.GetPrefix(key);
|
||||
|
||||
leaf.SetCount(count + 1);
|
||||
}
|
||||
|
||||
private static SplitResult<K> SplitLeaf<K, V, TStrategy>(LeafNode<K, V> left, int insertIndex, K key, V value, TStrategy strategy, OwnerId owner)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
var right = new LeafNode<K, V>(owner, strategy.UsesPrefixes);
|
||||
var right = new LeafNode<K, V>(owner);
|
||||
int totalCount = left.Header.Count;
|
||||
|
||||
int splitPoint = (insertIndex == totalCount) ? totalCount : (insertIndex == 0 ? 0 : totalCount / 2);
|
||||
int moveCount = totalCount - splitPoint;
|
||||
// Heuristics
|
||||
int splitPoint;
|
||||
if (insertIndex == totalCount) splitPoint = totalCount; // Append: Keep all in Left (90/10 logic effectively)
|
||||
else if (insertIndex == 0) splitPoint = 0; // Prepend: Right gets all
|
||||
else splitPoint = totalCount / 2;
|
||||
|
||||
// Move items to Right
|
||||
int moveCount = totalCount - splitPoint;
|
||||
if (moveCount > 0)
|
||||
{
|
||||
// Fast Span memory moves
|
||||
left.Keys.AsSpan(splitPoint, moveCount).CopyTo(right.Keys.AsSpan(0));
|
||||
left.Values.AsSpan(splitPoint, moveCount).CopyTo(right.Values.AsSpan(0));
|
||||
|
||||
// Manually copy prefixes if needed or re-calculate
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
left.AllPrefixes.Slice(splitPoint, moveCount).CopyTo(right.AllPrefixes);
|
||||
}
|
||||
for (int i = 0; i < moveCount; i++) right.AllPrefixes[i] = left.AllPrefixes[splitPoint + i];
|
||||
}
|
||||
|
||||
// Update Counts
|
||||
left.SetCount(splitPoint);
|
||||
right.SetCount(moveCount);
|
||||
|
||||
// Insert the New Item into the correct node
|
||||
if (insertIndex < splitPoint || (splitPoint == 0 && insertIndex == 0))
|
||||
{
|
||||
{
|
||||
InsertIntoLeaf(left, insertIndex, key, value, strategy);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// In B+ Tree, the separator is the first key of the right node
|
||||
return new SplitResult<K>(right, right.Keys[0]);
|
||||
}
|
||||
|
||||
|
|
@ -389,63 +427,89 @@ else
|
|||
{
|
||||
int count = node.Header.Count;
|
||||
|
||||
// Shift Keys and Prefixes
|
||||
if (index < count)
|
||||
{
|
||||
|
||||
int moveCount = count - index;
|
||||
Span<K> keysSpan = node.Keys;
|
||||
keysSpan.Slice(index, moveCount).CopyTo(keysSpan.Slice(index + 1));
|
||||
|
||||
Span<Node<K>> childrenSpan = node.Children;
|
||||
childrenSpan.Slice(index + 1, moveCount).CopyTo(childrenSpan.Slice(index + 2));
|
||||
// Fast Span memory moves
|
||||
node.Keys.AsSpan(index, moveCount).CopyTo(node.Keys.AsSpan(index + 1));
|
||||
|
||||
// FIX: Shift raw prefix array
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
node.AllPrefixes.Slice(index, moveCount).CopyTo(node.AllPrefixes.Slice(index + 1));
|
||||
node.AllPrefixes.Slice(index, count - index)
|
||||
.CopyTo(node.AllPrefixes.Slice(index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Shift Children
|
||||
// Children buffer is indexable like an array but requires manual loop or Unsafe copy
|
||||
// if we don't want to use unsafe pointers.
|
||||
// Since it's a small struct buffer (size 33), a loop is fine/fast.
|
||||
for (int i = count + 1; i > index + 1; i--)
|
||||
{
|
||||
node.Children[i] = node.Children[i - 1];
|
||||
}
|
||||
|
||||
node.Keys[index] = separator;
|
||||
node.Children[index + 1] = newChild;
|
||||
|
||||
// FIX: Write to raw array
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
node.AllPrefixes[index] = strategy.GetPrefix(separator);
|
||||
}
|
||||
node.AllPrefixes![index] = strategy.GetPrefix(separator);
|
||||
|
||||
node.Children[index + 1] = newChild;
|
||||
node.SetCount(count + 1);
|
||||
}
|
||||
|
||||
private static SplitResult<K> SplitInternal<K, TStrategy>(InternalNode<K> left, int insertIndex, K separator, Node<K> newChild, TStrategy strategy, OwnerId owner)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
var right = strategy.UsesPrefixes
|
||||
? new PrefixInternalNode<K>(owner)
|
||||
: new InternalNode<K>(owner);
|
||||
|
||||
var right = new InternalNode<K>(owner);
|
||||
int count = left.Header.Count;
|
||||
int splitPoint = count / 2;
|
||||
K upKey = left.Keys[splitPoint];
|
||||
int moveCount = count - splitPoint - 1;
|
||||
int splitPoint = count / 2; // Internal nodes usually split 50/50 to keep tree fat
|
||||
|
||||
// The key at splitPoint moves UP to become the separator.
|
||||
// Keys > splitPoint move to Right.
|
||||
|
||||
K upKey = left.Keys[splitPoint];
|
||||
|
||||
// Move Keys/Prefixes to Right
|
||||
int moveCount = count - splitPoint - 1; // -1 because splitPoint key goes up
|
||||
// Fast Span memory moves
|
||||
if (moveCount > 0)
|
||||
{
|
||||
Span<K> leftKeys = left.Keys;
|
||||
Span<K> rightKeys = right.Keys;
|
||||
leftKeys.Slice(splitPoint + 1, moveCount).CopyTo(rightKeys);
|
||||
|
||||
Span<Node<K>> leftChildren = left.Children;
|
||||
Span<Node<K>> rightChildren = right.Children;
|
||||
leftChildren.Slice(splitPoint + 1, moveCount + 1).CopyTo(rightChildren);
|
||||
left.Keys.AsSpan(splitPoint + 1, moveCount).CopyTo(right.Keys.AsSpan(0));
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
left.AllPrefixes.Slice(splitPoint + 1, moveCount).CopyTo(right.AllPrefixes);
|
||||
left.AllPrefixes.Slice(splitPoint + 1, moveCount).CopyTo(right.AllPrefixes.Slice(0));
|
||||
}
|
||||
}
|
||||
// Left has children 0..splitPoint. Right has children splitPoint+1..End
|
||||
for (int i = 0; i <= moveCount; i++)
|
||||
{
|
||||
right.Children[i] = left.Children[splitPoint + 1 + i];
|
||||
}
|
||||
|
||||
left.SetCount(splitPoint);
|
||||
right.SetCount(moveCount);
|
||||
|
||||
// Determine where to insert the new Separator/Child
|
||||
// Note: We extracted 'upKey' from the original array.
|
||||
// We now have to compare the *incoming* separator with 'upKey'
|
||||
// to see if it goes Left or Right.
|
||||
|
||||
if (insertIndex == splitPoint)
|
||||
{
|
||||
// Special case: The new key is exactly the one pushing up?
|
||||
// Usually easier to insert into temp buffer and split,
|
||||
// but here we can branch:
|
||||
// If insertIndex <= splitPoint, insert left. Else right.
|
||||
}
|
||||
|
||||
// Simplified insertion into split nodes:
|
||||
if (insertIndex <= splitPoint)
|
||||
{
|
||||
InsertIntoInternal(left, insertIndex, separator, newChild, strategy);
|
||||
|
|
@ -462,6 +526,11 @@ else
|
|||
// Removal Logic
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Removal Logic (Fixed Type Inference & Casting)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
|
||||
private static void RemoveFromLeaf<K, V, TStrategy>(LeafNode<K, V> leaf, int index, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
|
|
@ -470,11 +539,13 @@ else
|
|||
|
||||
if (moveCount > 0)
|
||||
{
|
||||
// Fast Span memory moves
|
||||
leaf.Keys.AsSpan(index + 1, moveCount).CopyTo(leaf.Keys.AsSpan(index));
|
||||
leaf.Values.AsSpan(index + 1, moveCount).CopyTo(leaf.Values.AsSpan(index));
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
// Replaced manual 'for' loop with native slice copy
|
||||
leaf.AllPrefixes.Slice(index + 1, moveCount).CopyTo(leaf.AllPrefixes.Slice(index));
|
||||
}
|
||||
}
|
||||
|
|
@ -482,9 +553,11 @@ else
|
|||
leaf.SetCount(count - 1);
|
||||
}
|
||||
|
||||
// FIX 3: Added <V> to HandleUnderflow
|
||||
private static bool HandleUnderflow<K, V, TStrategy>(InternalNode<K> parent, int childIndex, TStrategy strategy, OwnerId owner)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
// Try to borrow from Right Sibling
|
||||
if (childIndex < parent.Header.Count)
|
||||
{
|
||||
var rightSibling = parent.Children[childIndex + 1]!.EnsureEditable(owner);
|
||||
|
|
@ -502,6 +575,7 @@ else
|
|||
return parent.Header.Count < LeafNode<K, V>.MergeThreshold;
|
||||
}
|
||||
}
|
||||
// Try to borrow from Left Sibling
|
||||
else if (childIndex > 0)
|
||||
{
|
||||
var leftSibling = parent.Children[childIndex - 1]!.EnsureEditable(owner);
|
||||
|
|
@ -515,6 +589,7 @@ else
|
|||
}
|
||||
else
|
||||
{
|
||||
// Merge Left and Current. Note separator index is 'childIndex - 1'
|
||||
Merge<K, V, TStrategy>(parent, childIndex - 1, leftSibling, rightChild, strategy);
|
||||
return parent.Header.Count < LeafNode<K, V>.MergeThreshold;
|
||||
}
|
||||
|
|
@ -525,12 +600,15 @@ else
|
|||
|
||||
private static bool CanBorrow<K>(Node<K> node)
|
||||
{
|
||||
// Note: LeafNode<K, V>.MergeThreshold is constant 8, so we can access it statically or via 8
|
||||
return node.Header.Count > 8 + 1;
|
||||
}
|
||||
|
||||
// FIX 4: Added <V> to Merge/Rotate so we can cast to LeafNode<K, V> successfully.
|
||||
private static void Merge<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
// Case A: Merging Leaves
|
||||
if (left.IsLeaf)
|
||||
{
|
||||
var leftLeaf = left.AsLeaf<V>();
|
||||
|
|
@ -538,64 +616,64 @@ else
|
|||
|
||||
int lCount = leftLeaf.Header.Count;
|
||||
int rCount = rightLeaf.Header.Count;
|
||||
|
||||
rightLeaf.Keys.AsSpan(0, rCount).CopyTo(leftLeaf.Keys.AsSpan(lCount));
|
||||
rightLeaf.Values.AsSpan(0, rCount).CopyTo(leftLeaf.Values.AsSpan(lCount));
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
rightLeaf.AllPrefixes.Slice(0, rCount).CopyTo(leftLeaf.AllPrefixes.Slice(lCount));
|
||||
rightLeaf.AllPrefixes.Slice(0, rCount)
|
||||
.CopyTo(leftLeaf.AllPrefixes.Slice(lCount));
|
||||
}
|
||||
|
||||
leftLeaf.SetCount(lCount + rCount);
|
||||
}
|
||||
// Case B: Merging Internal Nodes
|
||||
else
|
||||
{
|
||||
var leftInternal = left.AsInternal();
|
||||
var rightInternal = right.AsInternal();
|
||||
|
||||
// Pull separator from parent
|
||||
K separator = parent.Keys[separatorIndex];
|
||||
|
||||
int lCount = leftInternal.Header.Count;
|
||||
leftInternal.Keys[lCount] = separator;
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
leftInternal.AllPrefixes[lCount] = strategy.GetPrefix(separator);
|
||||
}
|
||||
|
||||
int rCount = rightInternal.Header.Count;
|
||||
Span<K> rightKeys = rightInternal.Keys;
|
||||
Span<K> leftKeys = leftInternal.Keys;
|
||||
rightKeys.Slice(0, rCount).CopyTo(leftKeys.Slice(lCount + 1));
|
||||
|
||||
rightInternal.Keys.AsSpan(0, rCount).CopyTo(leftInternal.Keys.AsSpan(lCount + 1));
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
rightInternal.AllPrefixes.Slice(0, rCount).CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1));
|
||||
rightInternal.AllPrefixes.Slice(0, rCount)
|
||||
.CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1));
|
||||
}
|
||||
|
||||
Span<Node<K>> rightChildren = rightInternal.Children;
|
||||
Span<Node<K>> leftChildren = leftInternal.Children;
|
||||
rightChildren.Slice(0, rCount + 1).CopyTo(leftChildren.Slice(lCount + 1));
|
||||
for (int i = 0; i <= rCount; i++)
|
||||
{
|
||||
leftInternal.Children[lCount + 1 + i] = rightInternal.Children[i];
|
||||
}
|
||||
|
||||
leftInternal.SetCount(lCount + 1 + rCount);
|
||||
}
|
||||
|
||||
// Remove Separator and Right Child from Parent
|
||||
int pCount = parent.Header.Count;
|
||||
int moveCount = pCount - separatorIndex - 1;
|
||||
|
||||
if (moveCount > 0)
|
||||
{
|
||||
Span<K> parentKeys = parent.Keys;
|
||||
parentKeys.Slice(separatorIndex + 1, moveCount).CopyTo(parentKeys.Slice(separatorIndex));
|
||||
parent.Keys.AsSpan(separatorIndex + 1, moveCount).CopyTo(parent.Keys.AsSpan(separatorIndex));
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
// Replaced manual 'for' loop with native slice copy
|
||||
parent.AllPrefixes.Slice(separatorIndex + 1, moveCount).CopyTo(parent.AllPrefixes.Slice(separatorIndex));
|
||||
}
|
||||
}
|
||||
|
||||
Span<Node<K>> parentChildren = parent.Children;
|
||||
parentChildren.Slice(separatorIndex + 2, moveCount).CopyTo(parentChildren.Slice(separatorIndex + 1));
|
||||
for (int i = separatorIndex + 2; i <= pCount; i++)
|
||||
{
|
||||
parent.Children[i - 1] = parent.Children[i];
|
||||
}
|
||||
|
||||
parent.SetCount(pCount - 1);
|
||||
|
|
@ -604,47 +682,51 @@ else
|
|||
private static void RotateLeft<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
// Move one item from Right to Left
|
||||
if (left.IsLeaf)
|
||||
{
|
||||
var leftLeaf = left.AsLeaf<V>();
|
||||
var rightLeaf = right.AsLeaf<V>();
|
||||
|
||||
// Move first of right to end of left
|
||||
InsertIntoLeaf(leftLeaf, leftLeaf.Header.Count, rightLeaf.Keys[0], rightLeaf.Values[0], strategy);
|
||||
RemoveFromLeaf(rightLeaf, 0, strategy);
|
||||
|
||||
// Update Parent Separator
|
||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var leftInternal = left.AsInternal();
|
||||
var rightInternal = right.AsInternal();
|
||||
|
||||
// 1. Move Parent Separator to Left End
|
||||
K sep = parent.Keys[separatorIndex];
|
||||
InsertIntoInternal(leftInternal, leftInternal.Header.Count, sep, rightInternal.Children[0]!, strategy);
|
||||
|
||||
// 2. Move Right[0] Key to Parent
|
||||
parent.Keys[separatorIndex] = rightInternal.Keys[0];
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightInternal.Keys[0]);
|
||||
}
|
||||
|
||||
// 3. Fix Right (Remove key 0 and shift child 0 out)
|
||||
// We basically remove key at 0. Child 0 was moved to left. Child 1 becomes Child 0.
|
||||
// Re-using Remove logic implies shifts.
|
||||
// Manual shift for performance:
|
||||
int rCount = rightInternal.Header.Count;
|
||||
|
||||
Span<Node<K>> rightChildren = rightInternal.Children;
|
||||
rightChildren.Slice(1, rCount).CopyTo(rightChildren);
|
||||
|
||||
// Shift children
|
||||
for (int i = 0; i < rCount; i++) rightInternal.Children[i] = rightInternal.Children[i + 1];
|
||||
if (rCount > 1)
|
||||
{
|
||||
Span<K> rightKeys = rightInternal.Keys;
|
||||
rightKeys.Slice(1, rCount - 1).CopyTo(rightKeys);
|
||||
// Fast Span memory moves (Replaces Array.Copy & manual loop)
|
||||
rightInternal.Keys.AsSpan(1, rCount - 1).CopyTo(rightInternal.Keys.AsSpan(0));
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
rightInternal.AllPrefixes.Slice(1, rCount - 1).CopyTo(rightInternal.AllPrefixes);
|
||||
rightInternal.AllPrefixes.Slice(1, rCount - 1).CopyTo(rightInternal.AllPrefixes.Slice(0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -655,6 +737,7 @@ else
|
|||
private static void RotateRight<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
// Move one item from Left to Right
|
||||
if (left.IsLeaf)
|
||||
{
|
||||
var leftLeaf = left.AsLeaf<V>();
|
||||
|
|
@ -666,29 +749,31 @@ else
|
|||
|
||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||
}
|
||||
parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var leftInternal = left.AsInternal();
|
||||
var rightInternal = right.AsInternal();
|
||||
var leftInternal = (InternalNode<K>)left;
|
||||
var rightInternal = (InternalNode<K>)right;
|
||||
int last = leftInternal.Header.Count - 1;
|
||||
|
||||
// 1. Move Parent Separator to Right Start
|
||||
K sep = parent.Keys[separatorIndex];
|
||||
// The child moving to right is the *last* child of left (index count)
|
||||
InsertIntoInternal(rightInternal, 0, sep, leftInternal.Children[last + 1]!, strategy);
|
||||
|
||||
// 2. Move Left[last] Key to Parent
|
||||
parent.Keys[separatorIndex] = leftInternal.Keys[last];
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]);
|
||||
}
|
||||
parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]);
|
||||
|
||||
// 3. Truncate Left
|
||||
leftInternal.SetCount(last);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool TryGetMin<K, V>(Node<K> root, out K key, out V value)
|
||||
{
|
||||
var current = root;
|
||||
|
|
@ -705,7 +790,7 @@ else
|
|||
return false;
|
||||
}
|
||||
|
||||
key = leaf.Keys[0];
|
||||
key = leaf.Keys![0];
|
||||
value = leaf.Values[0];
|
||||
return true;
|
||||
}
|
||||
|
|
@ -728,7 +813,7 @@ else
|
|||
}
|
||||
|
||||
int last = leaf.Header.Count - 1;
|
||||
key = leaf.Keys[last];
|
||||
key = leaf.Keys![last];
|
||||
value = leaf.Values[last];
|
||||
return true;
|
||||
}
|
||||
|
|
@ -741,6 +826,7 @@ else
|
|||
int depth = 0;
|
||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||
|
||||
|
||||
var current = root;
|
||||
while (!current.IsLeaf)
|
||||
{
|
||||
|
|
@ -755,19 +841,23 @@ else
|
|||
var leaf = current.AsLeaf<V>();
|
||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||
|
||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) index++;
|
||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0) index++;
|
||||
|
||||
// 1. Successor is in the same leaf
|
||||
if (index < leaf.Header.Count)
|
||||
{
|
||||
nextKey = leaf.Keys[index];
|
||||
nextKey = leaf.Keys![index];
|
||||
nextValue = leaf.Values[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Successor is in the next leaf (We must backtrack up the tree!)
|
||||
for (int i = depth - 1; i >= 0; i--)
|
||||
{
|
||||
// If we haven't reached the right-most child of this parent
|
||||
if (indices[i] < path[i].Header.Count)
|
||||
{
|
||||
// Take one step right, then go absolute left all the way down
|
||||
current = path[i].Children[indices[i] + 1]!;
|
||||
while (!current.IsLeaf)
|
||||
{
|
||||
|
|
@ -775,7 +865,7 @@ else
|
|||
}
|
||||
|
||||
var targetLeaf = current.AsLeaf<V>();
|
||||
nextKey = targetLeaf.Keys[0];
|
||||
nextKey = targetLeaf.Keys![0];
|
||||
nextValue = targetLeaf.Values[0];
|
||||
return true;
|
||||
}
|
||||
|
|
@ -789,11 +879,13 @@ else
|
|||
public static bool TryGetPredecessor<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out K prevKey, out V prevValue)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
{
|
||||
// Max depth of a B-Tree is small, preallocate a small array to track the descent path.
|
||||
InternalNode<K>[] path = new InternalNode<K>[32];
|
||||
int[] indices = new int[32];
|
||||
int depth = 0;
|
||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||
|
||||
|
||||
var current = root;
|
||||
while (!current.IsLeaf)
|
||||
{
|
||||
|
|
@ -808,17 +900,20 @@ else
|
|||
var leaf = current.AsLeaf<V>();
|
||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||
|
||||
// Easy case: Predecessor is in the same leaf
|
||||
if (index > 0)
|
||||
{
|
||||
prevKey = leaf.Keys[index - 1];
|
||||
prevKey = leaf.Keys![index - 1];
|
||||
prevValue = leaf.Values[index - 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hard case: We need to backtrack to find the first left branch we ignored
|
||||
for (int i = depth - 1; i >= 0; i--)
|
||||
{
|
||||
if (indices[i] > 0)
|
||||
{
|
||||
// Jump to the left sibling branch, then take the absolute right-most path down
|
||||
current = path[i].Children[indices[i] - 1]!;
|
||||
while (!current.IsLeaf)
|
||||
{
|
||||
|
|
@ -828,7 +923,7 @@ else
|
|||
|
||||
var targetLeaf = current.AsLeaf<V>();
|
||||
int last = targetLeaf.Header.Count - 1;
|
||||
prevKey = targetLeaf.Keys[last];
|
||||
prevKey = targetLeaf.Keys![last];
|
||||
prevValue = targetLeaf.Values[last];
|
||||
return true;
|
||||
}
|
||||
|
|
@ -839,4 +934,6 @@ else
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ public abstract class BaseOrderedMap<K, V, TStrategy> : IEnumerable<KeyValuePair
|
|||
public static PersistentMap<K, V, TStrategy> Create(TStrategy strategy)
|
||||
{
|
||||
// Start with an empty leaf owned by None so the first write triggers CoW.
|
||||
var emptyRoot = new LeafNode<K, V>(OwnerId.None, strategy.UsesPrefixes);
|
||||
var emptyRoot = new LeafNode<K, V>(OwnerId.None);
|
||||
return new PersistentMap<K, V, TStrategy>(emptyRoot, strategy, 0);
|
||||
}
|
||||
|
||||
public static TransientMap<K, V, TStrategy> CreateTransient(TStrategy strategy)
|
||||
{
|
||||
var emptyRoot = new LeafNode<K, V>(OwnerId.None, strategy.UsesPrefixes);
|
||||
var emptyRoot = new LeafNode<K, V>(OwnerId.None);
|
||||
return new TransientMap<K, V, TStrategy>(emptyRoot, strategy,0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,38 @@ public interface IKeyStrategy<K>
|
|||
|
||||
//
|
||||
bool IsLossless => false;
|
||||
bool UseBinarySearch => false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A universal key strategy for any type that relies on standard comparisons
|
||||
/// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes.
|
||||
/// </summary>
|
||||
public readonly struct StandardStrategy<K> : IKeyStrategy<K>
|
||||
{
|
||||
private readonly IComparer<K> _comparer;
|
||||
|
||||
// If no comparer is provided, it defaults to Comparer<K>.Default
|
||||
// which automatically uses IComparable<K> if the type implements it.
|
||||
public StandardStrategy(IComparer<K>? comparer = null)
|
||||
{
|
||||
_comparer = comparer ?? Comparer<K>.Default;
|
||||
}
|
||||
|
||||
// Tell the B-Tree to skip SIMD routing and just use LinearSearch
|
||||
public bool UsesPrefixes => false;
|
||||
|
||||
// This will never be called because UsesPrefixes is false,
|
||||
// but we must satisfy the interface.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(K key) => 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(K x, K y)
|
||||
{
|
||||
return _comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct UnicodeStrategy : IKeyStrategy<string>
|
||||
|
|
@ -61,5 +89,159 @@ public struct UnicodeStrategy : IKeyStrategy<string>
|
|||
public bool UsesPrefixes => true;
|
||||
}
|
||||
|
||||
public struct IntStrategy : IKeyStrategy<int>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(int x, int y) => x.CompareTo(y);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(int key)
|
||||
{
|
||||
// Pack the 32-bit int into the high 32-bits of the long.
|
||||
// This preserves sorting order when scanning the long array.
|
||||
// Cast to uint first to prevent sign extension confusion during the shift,
|
||||
// though standard int shifting usually works fine for direct mapping.
|
||||
return (long)key << 32;
|
||||
}
|
||||
|
||||
public bool UsesPrefixes => true;
|
||||
|
||||
public bool IsLossless => true;
|
||||
}
|
||||
|
||||
public struct DoubleStrategy : IKeyStrategy<double>
|
||||
{
|
||||
public bool IsLossless => true;
|
||||
// Use the standard comparison for the fallback/refine step
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(double x, double y) => x.CompareTo(y);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(double key)
|
||||
{
|
||||
// 1. Bit Cast to Long (0 cost)
|
||||
long bits = Unsafe.As<double, long>(ref key);
|
||||
|
||||
// 2. The Magic Twist
|
||||
// If the sign bit (MSB) is set (negative), we flip ALL bits.
|
||||
// If the sign bit is clear (positive), we flip ONLY the sign bit.
|
||||
// This maps:
|
||||
// -Negative Max -> 0
|
||||
// -0 -> Midpoint
|
||||
// +Negative Max -> Max
|
||||
|
||||
long mask = (bits >> 63); // 0 for positive, -1 (All 1s) for negative
|
||||
|
||||
// If negative: bits ^ -1 = ~bits (Flip All)
|
||||
// If positive: bits ^ 0 = bits (Flip None)
|
||||
// Then we toggle the sign bit (0x8000...) to shift the range to signed long.
|
||||
|
||||
return (bits ^ (mask & 0x7FFFFFFFFFFFFFFF)) ^ unchecked((long)0x8000000000000000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper for SIMD accelerated prefix scanning.
|
||||
/// </summary>
|
||||
public static class PrefixScanner
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int FindFirstGreaterOrEqual(ReadOnlySpan<long> prefixes, long targetPrefix)
|
||||
{
|
||||
// Handle MinValue specifically to avoid underflow in (target - 1) logic
|
||||
// If target is MinValue, any value in prefixes is >= target.
|
||||
// So the first element (index 0) is the match.
|
||||
// TODO: evaluate if this is needed.
|
||||
//if (targetPrefix == long.MinValue)
|
||||
//{
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
// Fallback for short arrays or unsupported hardware
|
||||
if (!Avx2.IsSupported || prefixes.Length < 4)
|
||||
return LinearScan(prefixes, targetPrefix);
|
||||
|
||||
return Avx512F.IsSupported
|
||||
? ScanAvx512(prefixes, targetPrefix)
|
||||
: ScanAvx2(prefixes, targetPrefix);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearScan(ReadOnlySpan<long> prefixes, long target)
|
||||
{
|
||||
for (var i = 0; i < prefixes.Length; i++)
|
||||
if (prefixes[i] >= target)
|
||||
return i;
|
||||
return prefixes.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx2(ReadOnlySpan<long> prefixes, long target)
|
||||
{
|
||||
// Create a vector where every element is the target prefix
|
||||
var vTarget = Vector256.Create(target);
|
||||
var i = 0;
|
||||
var len = prefixes.Length;
|
||||
|
||||
// Process 4 longs at a time (256 bits)
|
||||
for (; i <= len - 4; i += 4)
|
||||
fixed (long* ptr = prefixes)
|
||||
{
|
||||
var vData = Avx2.LoadVector256(ptr + i);
|
||||
|
||||
// Compare: result is -1 (all 1s) if true, 0 if false
|
||||
// We want Data >= Target.
|
||||
// AVX2 CompareGreaterThan is for signed. Longs should be treated carefully,
|
||||
// but for text prefixes (positive), signed compare is usually sufficient.
|
||||
// Effectively: !(Data < Target) could be safer if signs vary,
|
||||
// but here we assume prefixes are derived from unsigned chars.
|
||||
// Standard AVX2 hack for CompareGreaterOrEqual (Signed):
|
||||
// No native _mm256_cmpge_epi64 in AVX2.
|
||||
// Use CompareGreaterThan(Data, Target - 1)
|
||||
var vResult = Avx2.CompareGreaterThan(vData, Vector256.Create(target - 1));
|
||||
|
||||
var mask = Avx2.MoveMask(vResult.AsByte());
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
// Identify the first set bit corresponding to a 64-bit element
|
||||
// MoveMask returns 32 bits (1 per byte). Each long is 8 bytes.
|
||||
// We check bits 0, 8, 16, 24.
|
||||
if ((mask & 0xFF) != 0) return i + 0;
|
||||
if ((mask & 0xFF00) != 0) return i + 1;
|
||||
if ((mask & 0xFF0000) != 0) return i + 2;
|
||||
return i + 3;
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScan(prefixes.Slice(i), target) + i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx512(ReadOnlySpan<long> prefixes, long target)
|
||||
{
|
||||
var vTarget = Vector512.Create(target);
|
||||
var i = 0;
|
||||
var len = prefixes.Length;
|
||||
|
||||
for (; i <= len - 8; i += 8)
|
||||
fixed (long* ptr = prefixes)
|
||||
{
|
||||
var vData = Avx512F.LoadVector512(ptr + i);
|
||||
// AVX512 has dedicated Compare Greater Than or Equal Long
|
||||
var mask = Avx512F.CompareGreaterThanOrEqual(vData, vTarget);
|
||||
|
||||
if (mask != Vector512<long>.Zero)
|
||||
{
|
||||
// Extract most significant bit mask
|
||||
var m = mask.ExtractMostSignificantBits();
|
||||
// Count trailing zeros to find the index
|
||||
return i + BitOperations.TrailingZeroCount(m);
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScan(prefixes.Slice(i), target) + i;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
namespace PersistentMap;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public struct DoubleStrategy : IKeyStrategy<double>
|
||||
{
|
||||
public bool IsLossless => true;
|
||||
// Use the standard comparison for the fallback/refine step
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(double x, double y) => x.CompareTo(y);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(double key)
|
||||
{
|
||||
// 1. Bit Cast to Long (0 cost)
|
||||
long bits = Unsafe.As<double, long>(ref key);
|
||||
|
||||
// 2. The Magic Twist
|
||||
// If the sign bit (MSB) is set (negative), we flip ALL bits.
|
||||
// If the sign bit is clear (positive), we flip ONLY the sign bit.
|
||||
// This maps:
|
||||
// -Negative Max -> 0
|
||||
// -0 -> Midpoint
|
||||
// +Negative Max -> Max
|
||||
|
||||
long mask = (bits >> 63); // 0 for positive, -1 (All 1s) for negative
|
||||
|
||||
// If negative: bits ^ -1 = ~bits (Flip All)
|
||||
// If positive: bits ^ 0 = bits (Flip None)
|
||||
// Then we toggle the sign bit (0x8000...) to shift the range to signed long.
|
||||
|
||||
return (bits ^ (mask & 0x7FFFFFFFFFFFFFFF)) ^ unchecked((long)0x8000000000000000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
namespace PersistentMap;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public struct IntStrategy : IKeyStrategy<int>
|
||||
{
|
||||
public bool UsesPrefixes => false;
|
||||
public bool IsLossless => true;
|
||||
public bool UseBinarySearch => false;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(int x, int y) => x.CompareTo(y);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(int key) => 0; // Unused
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
namespace PersistentMap;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86; // For AVX2
|
||||
using System.Numerics;
|
||||
/// <summary>
|
||||
/// Helper for SIMD accelerated prefix scanning.
|
||||
/// </summary>
|
||||
public static class PrefixScanner
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int FindFirstGreaterOrEqual(ReadOnlySpan<long> prefixes, long targetPrefix)
|
||||
{
|
||||
|
||||
// Fallback for short arrays or unsupported hardware
|
||||
if (!Avx2.IsSupported || prefixes.Length < 4)
|
||||
return LinearScan(prefixes, targetPrefix);
|
||||
|
||||
return Avx512F.IsSupported
|
||||
? ScanAvx512(prefixes, targetPrefix)
|
||||
: ScanAvx2(prefixes, targetPrefix);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearScan(ReadOnlySpan<long> prefixes, long target)
|
||||
{
|
||||
for (var i = 0; i < prefixes.Length; i++)
|
||||
if (prefixes[i] >= target)
|
||||
return i;
|
||||
return prefixes.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx2(ReadOnlySpan<long> prefixes, long target)
|
||||
{
|
||||
// Create a vector where every element is the target prefix
|
||||
var vTarget = Vector256.Create(target);
|
||||
var i = 0;
|
||||
var len = prefixes.Length;
|
||||
|
||||
// Process 4 longs at a time (256 bits)
|
||||
for (; i <= len - 4; i += 4)
|
||||
fixed (long* ptr = prefixes)
|
||||
{
|
||||
var vData = Avx2.LoadVector256(ptr + i);
|
||||
|
||||
// Compare: result is -1 (all 1s) if true, 0 if false
|
||||
// We want Data >= Target.
|
||||
// AVX2 CompareGreaterThan is for signed. Longs should be treated carefully,
|
||||
// but for text prefixes (positive), signed compare is usually sufficient.
|
||||
// Effectively: !(Data < Target) could be safer if signs vary,
|
||||
// but here we assume prefixes are derived from unsigned chars.
|
||||
// Standard AVX2 hack for CompareGreaterOrEqual (Signed):
|
||||
// No native _mm256_cmpge_epi64 in AVX2.
|
||||
// Use CompareGreaterThan(Data, Target - 1)
|
||||
var vResult = Avx2.CompareGreaterThan(vData, Vector256.Create(target - 1));
|
||||
|
||||
var mask = Avx2.MoveMask(vResult.AsByte());
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
// Identify the first set bit corresponding to a 64-bit element
|
||||
// MoveMask returns 32 bits (1 per byte). Each long is 8 bytes.
|
||||
// We check bits 0, 8, 16, 24.
|
||||
if ((mask & 0xFF) != 0) return i + 0;
|
||||
if ((mask & 0xFF00) != 0) return i + 1;
|
||||
if ((mask & 0xFF0000) != 0) return i + 2;
|
||||
return i + 3;
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScan(prefixes.Slice(i), target) + i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx512(ReadOnlySpan<long> prefixes, long target)
|
||||
{
|
||||
var vTarget = Vector512.Create(target);
|
||||
var i = 0;
|
||||
var len = prefixes.Length;
|
||||
|
||||
for (; i <= len - 8; i += 8)
|
||||
fixed (long* ptr = prefixes)
|
||||
{
|
||||
var vData = Avx512F.LoadVector512(ptr + i);
|
||||
// AVX512 has dedicated Compare Greater Than or Equal Long
|
||||
var mask = Avx512F.CompareGreaterThanOrEqual(vData, vTarget);
|
||||
|
||||
if (mask != Vector512<long>.Zero)
|
||||
{
|
||||
// Extract most significant bit mask
|
||||
var m = mask.ExtractMostSignificantBits();
|
||||
// Count trailing zeros to find the index
|
||||
return i + BitOperations.TrailingZeroCount(m);
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScan(prefixes.Slice(i), target) + i;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
namespace PersistentMap;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
/// <summary>
|
||||
/// A universal key strategy for any type that relies on standard comparisons
|
||||
/// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes.
|
||||
/// </summary>
|
||||
public readonly struct StandardStrategy<K> : IKeyStrategy<K>
|
||||
{
|
||||
private readonly IComparer<K> _comparer;
|
||||
|
||||
// If no comparer is provided, it defaults to Comparer<K>.Default
|
||||
// which automatically uses IComparable<K> if the type implements it.
|
||||
|
||||
public StandardStrategy()
|
||||
{
|
||||
_comparer = Comparer<K>.Default;
|
||||
}
|
||||
|
||||
public StandardStrategy(IComparer<K>? comparer)
|
||||
{
|
||||
_comparer = comparer ?? Comparer<K>.Default;
|
||||
}
|
||||
// Tell the B-Tree to skip SIMD routing and just use LinearSearch
|
||||
public bool UsesPrefixes => false;
|
||||
|
||||
// This will never be called because UsesPrefixes is false,
|
||||
// but we must satisfy the interface.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(K key) => 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(K x, K y)
|
||||
{
|
||||
return _comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,12 +32,6 @@ public struct NodeHeader
|
|||
}
|
||||
}
|
||||
|
||||
[InlineArray(32)]
|
||||
public struct KeyBuffer<K>
|
||||
{
|
||||
private K _element0;
|
||||
}
|
||||
|
||||
// Constraint: Internal Nodes fixed at 32 children.
|
||||
// This removes the need for a separate array allocation for children references.
|
||||
[InlineArray(32)]
|
||||
|
|
@ -88,12 +82,6 @@ public abstract class Node<K>
|
|||
// Zero-overhead cast. Assumes you checked !IsLeaf or know logic flow.
|
||||
return Unsafe.As<InternalNode<K>>(this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PrefixInternalNode<K> AsPrefixInternal()
|
||||
{
|
||||
return Unsafe.As<PrefixInternalNode<K>>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LeafNode<K, V> : Node<K>
|
||||
|
|
@ -108,14 +96,12 @@ public sealed class LeafNode<K, V> : Node<K>
|
|||
|
||||
public override Span<long> AllPrefixes => _prefixes != null ? _prefixes : Span<long>.Empty;
|
||||
|
||||
public LeafNode(OwnerId owner, bool usePrefixes) : base(owner, NodeFlags.IsLeaf | (usePrefixes ? NodeFlags.HasPrefixes : NodeFlags.None))
|
||||
public LeafNode(OwnerId owner) : base(owner, NodeFlags.IsLeaf | NodeFlags.HasPrefixes)
|
||||
{
|
||||
Keys = new K[Capacity];
|
||||
Values = new V[Capacity];
|
||||
if (usePrefixes)
|
||||
{
|
||||
_prefixes = new long[Capacity];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Copy Constructor for CoW
|
||||
|
|
@ -167,75 +153,67 @@ public sealed class LeafNode<K, V> : Node<K>
|
|||
}
|
||||
}
|
||||
|
||||
public class InternalNode<K> : Node<K>
|
||||
public sealed class InternalNode<K> : Node<K>
|
||||
{
|
||||
public const int Capacity = 32;
|
||||
|
||||
public KeyBuffer<K> Keys;
|
||||
// InlineArray storage
|
||||
internal InternalPrefixBuffer _prefixBuffer;
|
||||
public NodeBuffer<K> Children;
|
||||
|
||||
public override Span<long> AllPrefixes => Span<long>.Empty;
|
||||
|
||||
public InternalNode(OwnerId owner, NodeFlags flags = NodeFlags.None)
|
||||
: base(owner, flags)
|
||||
{
|
||||
}
|
||||
|
||||
// Fixed CoW Constructor
|
||||
protected InternalNode(InternalNode<K> original, OwnerId newOwner, NodeFlags flags)
|
||||
: base(newOwner, flags)
|
||||
{
|
||||
Header.Count = original.Header.Count;
|
||||
|
||||
// Fast struct blit for both Keys and Children.
|
||||
// No loop required for InlineArrays!
|
||||
this.Keys = original.Keys;
|
||||
this.Children = original.Children;
|
||||
}
|
||||
|
||||
// The missing method needed by BTreeFunctions for routing
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<Node<K>> GetChildren()
|
||||
{
|
||||
// An internal node always has (Count + 1) children
|
||||
return MemoryMarshal.CreateSpan(ref Children[0], Header.Count + 1);
|
||||
}
|
||||
|
||||
public override Span<K> GetKeys() => MemoryMarshal.CreateSpan(ref Keys[0], Header.Count);
|
||||
|
||||
public override Node<K> EnsureEditable(OwnerId transactionId)
|
||||
{
|
||||
if (transactionId == OwnerId.None) return new InternalNode<K>(this, OwnerId.None, Header.Flags);
|
||||
if (Header.Owner == transactionId) return this;
|
||||
return new InternalNode<K>(this, transactionId, Header.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed class PrefixInternalNode<K> : InternalNode<K>
|
||||
{
|
||||
internal InternalPrefixBuffer _prefixBuffer;
|
||||
public K[]? Keys;
|
||||
|
||||
public override Span<long> AllPrefixes => MemoryMarshal.CreateSpan(ref _prefixBuffer[0], Capacity);
|
||||
|
||||
public PrefixInternalNode(OwnerId owner)
|
||||
: base(owner, NodeFlags.HasPrefixes)
|
||||
public InternalNode(OwnerId owner) : base(owner, NodeFlags.HasPrefixes)
|
||||
{
|
||||
Keys = new K[Capacity];
|
||||
// Children buffer is a struct, zero-initialized by default
|
||||
}
|
||||
|
||||
// CoW Constructor
|
||||
private PrefixInternalNode(PrefixInternalNode<K> original, OwnerId newOwner)
|
||||
: base(original, newOwner, original.Header.Flags)
|
||||
// Copy Constructor for CoW
|
||||
private InternalNode(InternalNode<K> original, OwnerId newOwner)
|
||||
: base(newOwner, original.Header.Flags)
|
||||
{
|
||||
// Copy the base Keys and Children, then blit the prefix buffer
|
||||
Header.Count = original.Header.Count;
|
||||
Keys = new K[Capacity];
|
||||
Array.Copy(original.Keys, Keys, original.Header.Count);
|
||||
|
||||
// Fast struct blit for prefixes
|
||||
this._prefixBuffer = original._prefixBuffer;
|
||||
|
||||
var srcChildren = original.GetChildren();
|
||||
for (var i = 0; i < srcChildren.Length; i++) Children[i] = srcChildren[i];
|
||||
}
|
||||
|
||||
public override Node<K> EnsureEditable(OwnerId transactionId)
|
||||
{
|
||||
if (transactionId == OwnerId.None) return new PrefixInternalNode<K>(this, OwnerId.None);
|
||||
if (Header.Owner == transactionId) return this;
|
||||
return new PrefixInternalNode<K>(this, transactionId);
|
||||
if (transactionId == OwnerId.None)
|
||||
{
|
||||
return new InternalNode<K>(this, OwnerId.None);
|
||||
}
|
||||
|
||||
if (Header.Owner == transactionId)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return new InternalNode<K>(this, transactionId);
|
||||
}
|
||||
public override Span<K> GetKeys()
|
||||
{
|
||||
return Keys.AsSpan(0, Header.Count);
|
||||
}
|
||||
|
||||
// Exposes the InlineArray as a Span
|
||||
public Span<Node<K>?> GetChildren()
|
||||
{
|
||||
return MemoryMarshal.CreateSpan<Node<K>?>(ref Children[0]!, Header.Count + 1);
|
||||
}
|
||||
|
||||
public void SetChild(int index, Node<K> node)
|
||||
{
|
||||
Children[index] = node;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public sealed class PersistentMap<K, V, TStrategy> : BaseOrderedMap<K, V, TStrat
|
|||
// 'default(OwnerId)' (usually 0) marks this node as Immutable/Persistent.
|
||||
// This ensures that any subsequent Set/Remove will clone this node
|
||||
// instead of modifying it in place.
|
||||
var emptyRoot = new LeafNode<K, V>(default(OwnerId), strategy.UsesPrefixes);
|
||||
var emptyRoot = new LeafNode<K, V>(default(OwnerId));
|
||||
|
||||
return new PersistentMap<K, V, TStrategy>(emptyRoot, strategy, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ A high-performance, persistent (Copy-on-Write) B+ Tree implemented in C#.
|
|||
It is designed for zero-overhead reads, SIMD-accelerated key routing, and allocation-free range queries. It supports both fully immutable usage and "Transient" mode for high-throughput bulk mutations.
|
||||
|
||||
** Features
|
||||
- *Copy-on-Write Semantics*: Thread-safe, immutable tree states. Modifying the tre yields a new version while sharing unmodified nodes.
|
||||
- *Copy-on-Write Semantics*: Thread-safe, immutable tree states. Modifying the tree yields a new version while sharing unmodified nodes.
|
||||
- *Transient Mode*: Perform bulk mutations in-place with standard mutable performance, then freeze it into a =PersistentMap= in $O(1)$ time.
|
||||
- *SIMD Prefix Scanning*: Uses AVX2/AVX512 to vectorize B+ tree routing and binary searches via =long= key-prefixes.
|
||||
- *Linear Time Set Operations*: Sort-merge based =Intersect=, =Except=, and =SymmetricExcept= execute in $O(N+M)$ time using lazy evaluation.
|
||||
|
||||
|
||||
** When should I use this?
|
||||
Never, probably. This was just a fun little project. If you want a really fast immutable sorted map you should consider it. Despite this map being faster than LanguageExt.HashMap for some key types, you should definitely use that if you don't need a sorted collection. It is well tested and does have a very nice API, and it also has a performance model that is easy to understand. If you look at the performance characteristics of it below, it scales much more linear with collection size. For example: I don't know why PersistentMap lookups suddenly becomes so much slower when we reach 100000 integer keys. There might be a gazillion things like that, that make PersistentMap much slower for real world usage.
|
||||
Never, probably. This was just a fun little project. If you want a really fast immutable sorted map you should consider it. Despite this map being faster than LanguageExt.HashMap for some key types, you should definitely use that if you don't need a sorted collection. It is well tested and does not have any problems key collisions, which will slow this map down by a lot.
|
||||
|
||||
The general version of this, using =StandardStrategy<K>= does not benefit from the prefix optimization, although might benefit from a usage of binary search in the future.
|
||||
The general version of this, using =StandardStrategy<K>= does not benefit from the prefix optimization.
|
||||
|
||||
** Quick Start
|
||||
|
||||
|
|
@ -36,8 +36,8 @@ if (map2.TryGetValue(2, out var value))
|
|||
}
|
||||
#+end_src
|
||||
|
||||
*** 2. Transient Mode (Bulk Mutations
|
||||
If you need to insert thousands of elements, creating a new persistent tree on every insert is too slow. Use a =TransientMap= to mutate the tree in-place, then lock it into a persistent snapshot. This does not edit an existing map, but will make bulk operations a lot faster on nodes "owned" by the current map.
|
||||
*** 2. Transient Mode (Bulk Mutations)
|
||||
If you need to insert thousands of elements, creating a new persistent tree on every insert is too slow. Use a =TransientMap= to mutate the tree in-place, then lock it into a persistent snapshot.
|
||||
|
||||
#+begin_src csharp
|
||||
var transientMap = BaseOrderedMap<int, string, IntStrategy>.CreateTransient(new IntStrategy());
|
||||
|
|
@ -53,7 +53,7 @@ var persistentSnapshot = transientMap.ToPersistent();
|
|||
#+end_src
|
||||
|
||||
*** 3. Range Queries and Iteration
|
||||
Because it is a B+ tree range queries require zero allocations and simply walk the leaves.
|
||||
Because it is a B+ tree, leaf nodes are linked. Range queries require zero allocations and simply walk the leaves.
|
||||
|
||||
#+begin_src csharp
|
||||
var map = GetPopulatedMap();
|
||||
|
|
@ -68,7 +68,7 @@ foreach (var kvp in map.Range(min: 10, max: 50))
|
|||
var greaterThan100 = map.From(100);
|
||||
var lessThan50 = map.Until(50);
|
||||
var allElements = map.AsEnumerable();
|
||||
#+end_sr
|
||||
#+end_src
|
||||
|
||||
*** 4. Tree Navigation
|
||||
Find bounds and adjacent elements instantly. Missing keys will correctly resolve to the mathematical lower/upper bound.
|
||||
|
|
@ -91,7 +91,7 @@ if (map.TryGetPredecessor(42, out int prevKey, out string prevVal))
|
|||
#+end_src
|
||||
|
||||
*** 5. Set Operations
|
||||
Set operations take advantage of the tree's underlying sorted structure to merge trees in linear $O(N+M)$ time.
|
||||
Set operations take advantage of the tree's underlying sorted linked-list structure to merge trees in linear $O(N+M)$ time.
|
||||
|
||||
#+begin_src csharp
|
||||
var mapA = CreateMap(1, 2, 3, 4);
|
||||
|
|
@ -108,502 +108,146 @@ var symmetricDiff = mapA.SymmetricExcept(mapB);
|
|||
#+end_src
|
||||
|
||||
** Benchmarks
|
||||
These benchmarks tries a variety of operations. The Int benchmarks (the first ones) use avx for lookups. On a computer without avx this is bound to be slower. In benchmarks where there are many writes to the tree, the transient version is used, since this is the workload this datastructure is optimized for.
|
||||
This is going to be all over the place, but here is a small comparison to other immutable sequences. Due to how the prefix optimization works, this persistent map will be the absolutely most performant when there is high entropy in the first 8 bytes of the key. The following is pretty much the best scenario we can have since we probably only look at the first 8 characters (this is for reading a value).
|
||||
|
||||
Build: builds a map of size N. I did not benchmark the builders used by the built in collections, but they are almost certainly at least as fast as the transients used by this library. For integers, the map was sorted (triggering a small optimization in PersistentMap). For the string benchmark it was random.
|
||||
#+begin_src
|
||||
| Method | CollectionSize | KeySize | Mean | Gen0 | Allocated |
|
||||
|-----------------|----------------|----------|--------------:|-----------:|----------:|
|
||||
| PersistentMap | **1024** | **10** | **25.61 ns** | **0.0043** | **72 B** |
|
||||
| Sys.Sorted | 1024 | 10 | 153.18 ns | - | - |
|
||||
| LangExt.HashMap | 1024 | 10 | 24.80 ns | - | - |
|
||||
| LangExtSorted | 1024 | 10 | 176.90 ns | - | - |
|
||||
| PersistentMap | **1024** | **100** | **26.43 ns** | **0.0043** | **72 B** |
|
||||
| SysSorted | 1024 | 100 | 154.77 ns | - | - |
|
||||
| LangExt.HashMap | 1024 | 100 | 66.30 ns | - | - |
|
||||
| LangExtSorted | 1024 | 100 | 177.28 ns | - | - |
|
||||
| PersistentMap | **1024** | **1000** | **26.17 ns** | **0.0043** | **72 B** |
|
||||
| SysSorted | 1024 | 1000 | 155.68 ns | - | - |
|
||||
| LangExt.HashMap | 1024 | 1000 | 491.97 ns | - | - |
|
||||
| LangExtSorted | 1024 | 1000 | 181.58 ns | - | - |
|
||||
| PersistentMap | **131072** | **10** | **109.34 ns** | **0.0072** | **120 B** |
|
||||
| SysSorted | 131072 | 10 | 460.22 ns | - | - |
|
||||
| LangExt.HashMap | 131072 | 10 | 60.35 ns | - | - |
|
||||
| LangExtSorted | 131072 | 10 | 555.17 ns | - | - |
|
||||
| PersistentMap | **131072** | **100** | **147.30 ns** | **0.0072** | **120 B** |
|
||||
| SysSorted | 131072 | 100 | 556.39 ns | - | - |
|
||||
| LangExt.HashMap | 131072 | 100 | 162.81 ns | - | - |
|
||||
| LangExtSorted | 131072 | 100 | 605.15 ns | - | - |
|
||||
| PersistentMap | **131072** | **1000** | **170.16 ns** | **0.0072** | **120 B** |
|
||||
| SysSorted | 131072 | 1000 | 625.78 ns | - | - |
|
||||
| LangExt.HashMap | 131072 | 1000 | 763.75 ns | - | - |
|
||||
| LangExtSorted | 131072 | 1000 | 692.92 ns | - | - |
|
||||
|
||||
The retrieval benchmarks reads a subset of the keys in random order.
|
||||
|
||||
The update benchmarks updates a subset of the keys in random order.
|
||||
#+end_src
|
||||
|
||||
The update and set benchmarks updates and sets keys in random order. Half of the keys are new.
|
||||
|
||||
The iteration benchmarks iterate from start to finish. The ordered collections are of course in order.
|
||||
|
||||
The removal benchmarks removes a subset of the keys in random order.
|
||||
|
||||
*** Integer keys
|
||||
This is pretty much the best case scenario for everyone. Key comparisons are fast, hashing is minimal. For b+trees this means we can do a lot of key comparisons at once using avx. The machine this is done on is an amd 5900x, which supports some kind of bastardized avx512. It is not really a gain over avx256 in this benchmark though on that processor. With regards to building, the microsoft collections have builders, and they are about as fast as the TransientMap, but a little little slower on my computer. LanguageExt lacks transients, and thus these comparisons are _not_ fair.
|
||||
|
||||
ImmDict is System.Collections.Immutable dictionary. ImmSortedDict is it's sorted sibling. ExtMap is the sorted map from LanguageExt. ExtHashMap is the unsorted HashMap from LanguageExt.
|
||||
To look at pure overhead, here is a benchmark using integers as keys. This is also a good fit for this BTree, since it can utilize a key strategy that compares integers using AVX. The hash based alternatives are going to have a huge advantage here. As you can see, reading a single value isn't great, and setting a single value after building the btree is also pretty awful (setting many should probably be done using transients). Iterating a building (using transients. The only valid comparison here is probably to MS SortedDict) is however plenty fast.
|
||||
|
||||
|
||||
#+begin_src
|
||||
| Method | N | Mean | Gen0 | Gen1 | Gen2 | Allocated |
|
||||
|-------------------------|--------|-----------------:|-----------:|----------:|--------:|------------:|
|
||||
| Build_ImmDict | 100 | 11,307.04 ns | 4.9744 | 0.0458 | - | 41688 B |
|
||||
| Build_ImmSortedDict | 100 | 8,493.79 ns | 4.4250 | 0.0458 | - | 37104 B |
|
||||
| Build_ExtMap | 100 | 8,519.63 ns | 5.3101 | 0.0458 | - | 44432 B |
|
||||
| Build_ExtHashMap | 100 | 9,855.33 ns | 7.5378 | 0.0458 | - | 63104 B |
|
||||
| Build_PersistentMap | 100 | 8,698.33 ns | 16.3879 | 0.1526 | - | 137072 B |
|
||||
| Build_TransientMap | 100 | 1,665.90 ns | 0.6332 | 0.0038 | - | 5304 B |
|
||||
| Retrieve_ImmDict | 100 | 39.19 ns | - | - | - | - |
|
||||
| Retrieve_ImmSortedDict | 100 | 64.32 ns | - | - | - | - |
|
||||
| Retrieve_ExtMap | 100 | 117.61 ns | - | - | - | - |
|
||||
| Retrieve_ExtHashMap | 100 | 84.19 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap | 100 | 47.25 ns | - | - | - | - |
|
||||
| Update_ImmDict | 100 | 1,145.75 ns | 0.4616 | 0.0019 | - | 3872 B |
|
||||
| Update_PersistentMap | 100 | 1.107 μs | 1.9398 | 0.0248 | - | 15.86 KB |
|
||||
| Update_TransientMap | 100 | 347.49 ns | 0.3576 | 0.0033 | - | 2992 B |
|
||||
| Update_ImmSortedDict | 100 | 849.39 ns | 0.3958 | 0.0010 | - | 3312 B |
|
||||
| Update_ExtMap | 100 | 642.50 ns | 0.3939 | 0.0010 | - | 3296 B |
|
||||
| Update_ExtHashMap | 100 | 541.84 ns | 0.5283 | 0.0010 | - | 4424 B |
|
||||
| UpdateSet_ImmDict | 100 | 1,236.82 ns | 0.5226 | 0.0019 | - | 4376 B |
|
||||
| UpdateSet_PersistentMap | 100 | 1189 ns | 1.9398 | 0.0248 | - | 15.86 KB |
|
||||
| UpdateSet_TransientMap | 100 | 380.70 ns | 0.3576 | 0.0033 | - | 2992 B |
|
||||
| UpdateSet_ImmSortedDict | 100 | 887.07 ns | 0.4587 | 0.0010 | - | 3840 B |
|
||||
| UpdateSet_ExtMap | 100 | 856.76 ns | 0.4797 | 0.0010 | - | 4016 B |
|
||||
| UpdateSet_ExtHashMap | 100 | 582.31 ns | 0.5312 | 0.0019 | - | 4448 B |
|
||||
| Iterate_ImmDict | 100 | 1,324.41 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap | 100 | 175.98 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 100 | 488.69 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 100 | 337.40 ns | 0.0038 | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 100 | 1,209.77 ns | 0.2518 | - | - | 2112 B |
|
||||
| Remove_ImmDict | 100 | 899.57 ns | 0.4425 | 0.0010 | - | 3704 B |
|
||||
| Remove_TransientMap | 100 | 433.52 ns | 0.3290 | 0.0029 | - | 2752 B |
|
||||
| Remove_ImmSortedDict | 100 | 728.77 ns | 0.3786 | 0.0010 | - | 3168 B |
|
||||
| Remove_ExtMap | 100 | 653.72 ns | 0.3767 | 0.0010 | - | 3152 B |
|
||||
| Remove_ExtHashMap | 100 | 589.03 ns | 0.5178 | - | - | 4336 B |
|
||||
| Build_ImmDict | 1000 | 168,692.47 ns | 71.5332 | 6.8359 | - | 598712 B |
|
||||
| Build_ImmSortedDict | 1000 | 125,591.01 ns | 62.9883 | 5.1270 | - | 526896 B |
|
||||
| Build_ExtMap | 1000 | 117,763.81 ns | 72.3877 | 6.1035 | - | 605936 B |
|
||||
| Build_ExtHashMap | 1000 | 64,443.19 ns | 67.5049 | 1.5869 | - | 564864 B |
|
||||
| Build_PersistentMap | 1000 | 133,156.05 ns | 192.1387 | 7.8125 | - | 1607744 B |
|
||||
| Build_TransientMap | 1000 | 25,945.72 ns | 4.2725 | 0.1526 | - | 35976 B |
|
||||
| Retrieve_ImmDict | 1000 | 686.48 ns | - | - | - | - |
|
||||
| Retrieve_ImmSortedDict | 1000 | 1,145.83 ns | - | - | - | - |
|
||||
| Retrieve_ExtMap | 1000 | 2,276.07 ns | - | - | - | - |
|
||||
| Retrieve_ExtHashMap | 1000 | 808.53 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap | 1000 | 680.50 ns | - | - | - | - |
|
||||
| Update_ImmDict | 1000 | 16,863.81 ns | 6.5613 | 0.2136 | - | 54960 B |
|
||||
| Update_PersistentMap | 1000 | 13,617.12 ns | 19.4092 | 1.1597 | - | 158.59 KB |
|
||||
| Update_TransientMap | 1000 | 3,611.03 ns | 2.5406 | 0.1564 | - | 21280 B |
|
||||
| Update_ImmSortedDict | 1000 | 12,428.90 ns | 5.5542 | 0.1526 | - | 46464 B |
|
||||
| Update_ExtMap | 1000 | 10,091.51 ns | 5.6000 | 0.1678 | - | 46880 B |
|
||||
| Update_ExtHashMap | 1000 | 6,758.96 ns | 7.9575 | 0.2136 | - | 66616 B |
|
||||
| UpdateSet_ImmDict | 1000 | 21,489.70 ns | 7.0496 | 0.1831 | - | 59160 B |
|
||||
| UpdateSet_PersistentMap | 1000 | 14,890.21 ns | 19.4855 | 1.0529 | - | 159.23 KB |
|
||||
| UpdateSet_TransientMap | 1000 | 5,063.11 ns | 2.4796 | 0.1450 | - | 20776 B |
|
||||
| UpdateSet_ImmSortedDict | 1000 | 13,333.61 ns | 6.3782 | 0.1526 | - | 53472 B |
|
||||
| UpdateSet_ExtMap | 1000 | 11,221.39 ns | 6.5918 | 0.1526 | - | 55184 B |
|
||||
| UpdateSet_ExtHashMap | 1000 | 15,967.43 ns | 13.1836 | 0.4578 | - | 110440 B |
|
||||
| Iterate_ImmDict | 1000 | 15,325.93 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap | 1000 | 1,574.20 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 1000 | 5,110.07 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 1000 | 3,432.88 ns | 0.0038 | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 1000 | 8,207.75 ns | 0.2441 | - | - | 2112 B |
|
||||
| Remove_ImmDict | 1000 | 15,205.95 ns | 6.4392 | 0.2136 | - | 54064 B |
|
||||
| Remove_TransientMap | 1000 | 4,036.18 ns | 2.2507 | 0.1373 | - | 18880 B |
|
||||
| Remove_ImmSortedDict | 1000 | 10,664.14 ns | 5.7068 | 0.1678 | - | 47760 B |
|
||||
| Remove_ExtMap | 1000 | 9,993.90 ns | 5.5084 | 0.1526 | - | 46160 B |
|
||||
| Remove_ExtHashMap | 1000 | 7,475.24 ns | 7.7209 | 0.1907 | - | 64608 B |
|
||||
| Build_ImmDict | 10000 | 2,571,753.12 ns | 41.4063 | 390.6250 | - | 7882552 B |
|
||||
| Build_ImmSortedDict | 10000 | 1,975,364.14 ns | 820.3125 | 296.8750 | - | 6893616 B |
|
||||
| Build_ExtMap | 10000 | 1,866,221.83 ns | 917.9688 | 320.3125 | - | 7692272 B |
|
||||
| Build_ExtHashMap | 10000 | 1,215,103.58 ns | 1009.7656 | 240.2344 | - | 8446080 B |
|
||||
| Build_PersistentMap | 10000 | 1,930,457.96 ns | 2345.7031 | 494.1406 | - | 19626728 B |
|
||||
| Build_TransientMap | 10000 | 640,413.08 ns | 41.0156 | 8.7891 | - | 347344 B |
|
||||
| Retrieve_ImmDict | 10000 | 14,880.32 ns | - | - | - | - |
|
||||
| Retrieve_ImmSortedDict | 10000 | 15,595.68 ns | - | - | - | - |
|
||||
| Retrieve_ExtMap | 10000 | 36,225.60 ns | - | - | - | - |
|
||||
| Retrieve_ExtHashMap | 10000 | 11,987.70 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap | 10000 | 10,227.73 ns | - | - | - | - |
|
||||
| Update_ImmDict | 10000 | 318,905.00 ns | 86.9141 | 23.4375 | - | 730200 B |
|
||||
| Update_PersistentMap | 10000 | 202,244.42 ns | 243.6523 | 73.2422 | - | 1992.19 KB |
|
||||
| Update_TransientMap | 10000 | 73,203.50 ns | 24.9023 | 7.3242 | - | 209056 B |
|
||||
| Update_ImmSortedDict | 10000 | 216,638.87 ns | 77.1484 | 16.1133 | - | 645360 B |
|
||||
| Update_ExtMap | 10000 | 176,737.24 ns | 74.4629 | 17.5781 | - | 623600 B |
|
||||
| Update_ExtHashMap | 10000 | 105,445.84 ns | 97.2900 | 17.3340 | - | 814376 B |
|
||||
| UpdateSet_ImmDict | 10000 | 333,260.72 ns | 92.2852 | 19.0430 | - | 775784 B |
|
||||
| UpdateSet_PersistentMap | 10000 | 221,958.91 ns | 244.6289 | 71.2891 | - | 1998.95 KB |
|
||||
| UpdateSet_TransientMap | 10000 | 93,484.07 ns | 24.9023 | 7.3242 | - | 209072 B |
|
||||
| UpdateSet_ImmSortedDict | 10000 | 224,214.31 ns | 83.2520 | 14.6484 | - | 697920 B |
|
||||
| UpdateSet_ExtMap | 10000 | 186,761.55 ns | 83.7402 | 14.4043 | - | 700880 B |
|
||||
| UpdateSet_ExtHashMap | 10000 | 112,371.27 ns | 97.7783 | 20.2637 | - | 818240 B |
|
||||
| Iterate_ImmDict | 10000 | 152,686.50 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap | 10000 | 14,841.56 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 10000 | 53,372.05 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 10000 | 38,673.93 ns | - | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 10000 | 111,676.15 ns | 8.0566 | - | - | 67648 B |
|
||||
| Remove_ImmDict | 10000 | 303,798.22 ns | 86.4258 | 19.5313 | - | 726560 B |
|
||||
| Remove_TransientMap | 10000 | 58,890.93 ns | 22.0947 | 6.5308 | - | 185056 B |
|
||||
| Remove_ImmSortedDict | 10000 | 219,974.63 ns | 77.8809 | 15.1367 | - | 653184 B |
|
||||
| Remove_ExtMap | 10000 | 188,713.80 ns | 74.2188 | 14.4043 | - | 621248 B |
|
||||
| Remove_ExtHashMap | 10000 | 120,113.97 ns | 95.9473 | 15.9912 | - | 802944 B |
|
||||
| Build_ImmDict | 100000 | 38,394,437.95 ns | 11714.2857 | 1071.4286 | 71.4286 | 97460075 B |
|
||||
| Build_ImmSortedDict | 100000 | 30,860,676.12 ns | 10187.5000 | 906.2500 | 62.5000 | 84908636 B |
|
||||
| Build_ExtMap | 100000 | 28,415,796.22 ns | 11156.2500 | 937.5000 | 62.5000 | 92907004 B |
|
||||
| Build_ExtHashMap | 100000 | 29,149,824.12 ns | 15375.0000 | 2750.0000 | 62.5000 | 128198060 B |
|
||||
| Build_PersistentMap | 100000 | 24,745,757.59 ns | 27687.5000 | 375.0000 | - | 231722008 B |
|
||||
| Build_TransientMap | 100000 | 9,137,195.74 ns | 406.2500 | 234.3750 | - | 3460512 B |
|
||||
| Retrieve_ImmDict | 100000 | 1,259,618.31 ns | - | - | - | - |
|
||||
| Retrieve_ImmSortedDict | 100000 | 975,518.14 ns | - | - | - | - |
|
||||
| Retrieve_ExtMap | 100000 | 1,535,487.85 ns | - | - | - | - |
|
||||
| Retrieve_ExtHashMap | 100000 | 284,590.55 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap | 100000 | 429,001.27 ns | - | - | - | - |
|
||||
| Update_ImmDict | 100000 | 5,705,786.31 ns | 1093.7500 | 906.2500 | - | 9183488 B |
|
||||
| Update_PersistentMap | 100000 | 4,056,612.12 ns | 2945.3125 | 2781.2500 | 15.6250 | 23984.39 KB |
|
||||
| Update_TransientMap | 100000 | 1,145,551.13 ns | 248.0469 | 199.2188 | - | 2081568 B |
|
||||
| Update_ImmSortedDict | 100000 | 4,433,611.11 ns | 953.1250 | 796.8750 | - | 8021136 B |
|
||||
| Update_ExtMap | 100000 | 3,901,065.86 ns | 937.5000 | 789.0625 | - | 7848704 B |
|
||||
| Update_ExtHashMap | 100000 | 2,696,228.39 ns | 1289.0625 | 960.9375 | - | 10805952 B |
|
||||
| UpdateSet_ImmDict | 100000 | 5,340,382.88 ns | 1109.3750 | 867.1875 | - | 9318896 B |
|
||||
| UpdateSet_PersistentMap | 100000 | 4,629,564.21 ns | 2984.3750 | 1906.2500 | 39.0625 | 24060.44 KB |
|
||||
| UpdateSet_TransientMap | 100000 | 1,332,859.76 ns | 250.0000 | 208.9844 | - | 2099520 B |
|
||||
| UpdateSet_ImmSortedDict | 100000 | 4,418,076.49 ns | 1000.0000 | 992.1875 | - | 8396544 B |
|
||||
| UpdateSet_ExtMap | 100000 | 3,107,339.72 ns | 996.0938 | 507.8125 | - | 8349248 B |
|
||||
| UpdateSet_ExtHashMap | 100000 | 2,630,473.81 ns | 1292.9688 | 976.5625 | - | 10845480 B |
|
||||
| Iterate_ImmDict | 100000 | 1,550,040.28 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap | 100000 | 149,743.16 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 100000 | 723,978.27 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 100000 | 504,204.91 ns | - | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 100000 | 1,936,574.10 ns | 257.8125 | - | - | 2164800 B |
|
||||
| Remove_ImmDict | 100000 | 5,419,879.00 ns | 1093.7500 | 914.0625 | - | 9149160 B |
|
||||
| Remove_TransientMap | 100000 | 951,332.63 ns | 219.7266 | 155.2734 | - | 1839264 B |
|
||||
| Remove_ImmSortedDict | 100000 | 4,203,794.51 ns | 953.1250 | 781.2500 | - | 8028144 B |
|
||||
| Remove_ExtMap | 100000 | 3,896,109.04 ns | 929.6875 | 789.0625 | - | 7824560 B |
|
||||
| Remove_ExtHashMap | 100000 | 2,816,957.99 ns | 1277.3438 | 914.0625 | - | 10709360 B |
|
||||
|-------------------------------------|--------|-----------------:|----------:|----------:|--------:|------------:|
|
||||
| 'Build: PersistentMap (Transient)' | 100 | 3,764.63 ns | 0.3929 | 0.0038 | - | 6632 B |
|
||||
| 'Build: MS Sorted (Builder)' | 100 | 3,096.11 ns | 0.2899 | 0.0038 | - | 4864 B |
|
||||
| 'Build: LanguageExt Map (AVL)' | 100 | 6,967.02 ns | 2.2736 | 0.0229 | - | 38144 B |
|
||||
| 'Build: LanguageExt HashMap' | 100 | 4,594.07 ns | 1.9684 | 0.0076 | - | 33024 B |
|
||||
| 'Read: PersistentMap' | 100 | 1,596.68 ns | 0.4292 | - | - | 7200 B |
|
||||
| 'Read: MS Sorted' | 100 | 474.54 ns | - | - | - | - |
|
||||
| 'Read: LanguageExt Map' | 100 | 1,311.31 ns | - | - | - | - |
|
||||
| 'Read: LanguageExt HashMap' | 100 | 641.22 ns | - | - | - | - |
|
||||
| 'Iterate: PersistentMap' | 100 | 135.41 ns | - | - | - | - |
|
||||
| 'Iterate: MS Sorted' | 100 | 372.31 ns | - | - | - | - |
|
||||
| 'Iterate: LanguageExt Map' | 100 | 287.33 ns | 0.0019 | - | - | 32 B |
|
||||
| 'Iterate: LanguageExt HashMap' | 100 | 781.56 ns | 0.0648 | - | - | 1088 B |
|
||||
| 'Set: PersistentMap' | 100 | 85.68 ns | 0.1142 | 0.0007 | - | 1912 B |
|
||||
| 'Set: MS Sorted' | 100 | 66.44 ns | 0.0229 | - | - | 384 B |
|
||||
| 'Set: LanguageExt Map' | 100 | 60.04 ns | 0.0219 | - | - | 368 B |
|
||||
| 'Set: LanguageExt HashMap' | 100 | 36.62 ns | 0.0206 | - | - | 344 B |
|
||||
| 'Build: PersistentMap (Transient)' | 1000 | 49,445.56 ns | 3.1738 | 0.2441 | - | 53096 B |
|
||||
| 'Build: MS Sorted (Builder)' | 1000 | 50,163.19 ns | 2.8687 | 0.4272 | - | 48064 B |
|
||||
| 'Build: LanguageExt Map (AVL)' | 1000 | 103,877.98 ns | 34.6680 | 3.1738 | - | 580688 B |
|
||||
| 'Build: LanguageExt HashMap' | 1000 | 124,339.17 ns | 45.4102 | 3.2959 | - | 760096 B |
|
||||
| 'Read: PersistentMap' | 1000 | 17,671.71 ns | 4.3030 | - | - | 72000 B |
|
||||
| 'Read: MS Sorted' | 1000 | 7,911.72 ns | - | - | - | - |
|
||||
| 'Read: LanguageExt Map' | 1000 | 20,187.52 ns | - | - | - | - |
|
||||
| 'Read: LanguageExt HashMap' | 1000 | 9,740.28 ns | - | - | - | - |
|
||||
| 'Iterate: PersistentMap' | 1000 | 1,217.47 ns | - | - | - | - |
|
||||
| 'Iterate: MS Sorted' | 1000 | 3,875.47 ns | - | - | - | - |
|
||||
| 'Iterate: LanguageExt Map' | 1000 | 2,862.82 ns | - | - | - | 32 B |
|
||||
| 'Iterate: LanguageExt HashMap' | 1000 | 11,974.93 ns | 1.9226 | - | - | 32320 B |
|
||||
| 'Set: PersistentMap' | 1000 | 121.01 ns | 0.1142 | 0.0007 | - | 1912 B |
|
||||
| 'Set: MS Sorted' | 1000 | 91.62 ns | 0.0315 | - | - | 528 B |
|
||||
| 'Set: LanguageExt Map' | 1000 | 82.26 ns | 0.0305 | - | - | 512 B |
|
||||
| 'Set: LanguageExt HashMap' | 1000 | 57.02 ns | 0.0367 | - | - | 616 B |
|
||||
| 'Build: PersistentMap (Transient)' | 100000 | 10,808,233.62 ns | 296.8750 | 218.7500 | - | 5185832 B |
|
||||
| 'Build: MS Sorted (Builder)' | 100000 | 16,655,882.43 ns | 281.2500 | 250.0000 | - | 4800064 B |
|
||||
| 'Build: LanguageExt Map (AVL)' | 100000 | 39,932,734.83 ns | 5333.3333 | 3333.3333 | - | 89959040 B |
|
||||
| 'Build: LanguageExt HashMap' | 100000 | 21,220,179.10 ns | 5781.2500 | 2968.7500 | 31.2500 | 96555422 B |
|
||||
| 'Read: PersistentMap' | 100000 | 7,359,807.97 ns | 710.9375 | - | - | 12000000 B |
|
||||
| 'Read: MS Sorted' | 100000 | 8,428,009.48 ns | - | - | - | - |
|
||||
| 'Read: LanguageExt Map' | 100000 | 10,268,884.43 ns | - | - | - | - |
|
||||
| 'Read: LanguageExt HashMap' | 100000 | 1,936,555.07 ns | - | - | - | - |
|
||||
| 'Iterate: PersistentMap' | 100000 | 151,028.79 ns | - | - | - | - |
|
||||
| 'Iterate: MS Sorted' | 100000 | 1,068,072.16 ns | - | - | - | - |
|
||||
| 'Iterate: LanguageExt Map' | 100000 | 837,677.39 ns | - | - | - | 32 B |
|
||||
| 'Iterate: LanguageExt HashMap' | 100000 | 1,226,773.82 ns | 64.4531 | - | - | 1082432 B |
|
||||
| 'Set: PersistentMap' | 100000 | 208.61 ns | 0.1984 | 0.0024 | - | 3320 B |
|
||||
| 'Set: MS Sorted' | 100000 | 138.82 ns | 0.0458 | - | - | 768 B |
|
||||
| 'Set: LanguageExt Map' | 100000 | 128.28 ns | 0.0448 | - | - | 752 B |
|
||||
| 'Set: LanguageExt HashMap' | 100000 | 84.33 ns | 0.0583 | - | - | 976 B |
|
||||
|
||||
|
||||
#+end_src
|
||||
|
||||
* String keys
|
||||
|
||||
These benchmarks act like above, but do not insert keys in a specific order. Sorting them before will yield a speed boost. One uses the standardkeystrategy (does linear string comparisons) and one uses the unicodstrategy which encodes the first 8 bytes as a long and uses avx to search for keys.
|
||||
Lastly, here is a comparison of how things look compared to itself for when the prefixes are turned off for strings. This relies on regular linear string searches. This is however STILL a pretty good benchmark for all ordered dicts, since the strings are random, meaning the string comparison can stop almost immediately. For real world keys, all hash based dicts will be better, with everything regarding getting or setting a single key.
|
||||
|
||||
|
||||
#+begin_src
|
||||
```
|
||||
|
||||
BenchmarkDotNet v0.15.8, Linux Fedora Linux 43 (Container Image)
|
||||
AMD Ryzen 9 5900X 3.69GHz, 1 CPU, 24 logical and 12 physical cores
|
||||
.NET SDK 10.0.104
|
||||
[Host] : .NET 10.0.4 (10.0.4, 10.0.426.12010), X64 RyuJIT x86-64-v3
|
||||
ShortRun : .NET 10.0.4 (10.0.4, 10.0.426.12010), X64 RyuJIT x86-64-v3
|
||||
|
||||
Job=ShortRun IterationCount=3 LaunchCount=1
|
||||
WarmupCount=3
|
||||
|
||||
```
|
||||
| Method | N | StringLength | Mean | Gen0 | Gen1 | Gen2 | Allocated |
|
||||
|--------------------------------- |------- |------------- |------------------:|-----------:|----------:|---------:|------------:|
|
||||
| Build_TransientMap_Standard | 100 | 8 | 42,942.85 ns | 0.7324 | - | - | 6216 B |
|
||||
| Build_TransientMap_Unicode | 100 | 8 | 12,289.81 ns | 0.8850 | 0.0153 | - | 7528 B |
|
||||
| Build_ImmDict | 100 | 8 | 14,166.14 ns | 5.4016 | 0.0610 | - | 45280 B |
|
||||
| Build_ImmSortedDict | 100 | 8 | 22,037.60 ns | 4.2114 | 0.0305 | - | 35472 B |
|
||||
| Build_ExtMap | 100 | 8 | 22,837.07 ns | 5.6152 | 0.0610 | - | 47104 B |
|
||||
| Build_ExtHashMap | 100 | 8 | 8,907.91 ns | 3.9673 | 0.0305 | - | 33240 B |
|
||||
| Retrieve_ImmDict | 100 | 8 | 80.81 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 100 | 8 | 4,956.86 ns | 0.0534 | - | - | 480 B |
|
||||
| Retrieve_PersistentMap_Unicode | 100 | 8 | 148.61 ns | - | - | - | - |
|
||||
| Update_ImmDict | 100 | 8 | 1,089.03 ns | 0.4215 | - | - | 3536 B |
|
||||
| Update_PersistentMap_Standard | 100 | 8 | 8,182.15 ns | 2.3956 | 0.0305 | - | 20160 B |
|
||||
| Update_PersistentMap_Unicode | 100 | 8 | 1,954.07 ns | 2.7046 | 0.0496 | - | 22640 B |
|
||||
| Update_TransientMap_Standard | 100 | 8 | 7,007.11 ns | 0.4349 | - | - | 3640 B |
|
||||
| Update_TransientMap_Unicode | 100 | 8 | 667.55 ns | 0.4644 | 0.0057 | - | 3888 B |
|
||||
| Update_ImmSortedDict | 100 | 8 | 1,741.49 ns | 0.3662 | - | - | 3072 B |
|
||||
| Update_ExtMap | 100 | 8 | 1,812.71 ns | 0.4120 | - | - | 3456 B |
|
||||
| Update_ExtHashMap | 100 | 8 | 767.69 ns | 0.5360 | 0.0010 | - | 4488 B |
|
||||
| UpdateSet_ImmDict | 100 | 8 | 1,508.45 ns | 0.5589 | 0.0019 | - | 4688 B |
|
||||
| UpdateSet_PersistentMap_Standard | 100 | 8 | 6,877.98 ns | 2.4033 | 0.0381 | - | 20160 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 100 | 8 | 2,660.42 ns | 2.7046 | 0.0534 | - | 22640 B |
|
||||
| UpdateSet_TransientMap_Standard | 100 | 8 | 5,415.27 ns | 0.4349 | - | - | 3640 B |
|
||||
| UpdateSet_TransientMap_Unicode | 100 | 8 | 1,212.47 ns | 0.4635 | 0.0057 | - | 3888 B |
|
||||
| UpdateSet_ImmSortedDict | 100 | 8 | 2,424.14 ns | 0.4578 | - | - | 3840 B |
|
||||
| UpdateSet_ExtMap | 100 | 8 | 2,111.13 ns | 0.4730 | - | - | 3960 B |
|
||||
| UpdateSet_ExtHashMap | 100 | 8 | 876.06 ns | 0.5646 | 0.0019 | - | 4728 B |
|
||||
| Iterate_ImmDict | 100 | 8 | 1,272.01 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap_Standard | 100 | 8 | 191.98 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 100 | 8 | 475.35 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 100 | 8 | 322.36 ns | 0.0038 | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 100 | 8 | 1,179.25 ns | 0.2747 | - | - | 2304 B |
|
||||
| Iterate_PersistentMap_Unicode | 100 | 8 | 191.41 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 100 | 8 | 1,420.61 ns | 0.5360 | 0.0019 | - | 4496 B |
|
||||
| Remove_PersistentMap_Standard | 100 | 8 | 8,294.72 ns | 2.4261 | 0.0305 | - | 20400 B |
|
||||
| Remove_PersistentMap_Unicode | 100 | 8 | 2,597.23 ns | 2.6779 | 0.0496 | - | 22400 B |
|
||||
| Remove_TransientMap_Standard | 100 | 8 | 7,178.94 ns | 0.4654 | - | - | 3928 B |
|
||||
| Remove_TransientMap_Unicode | 100 | 8 | 1,446.12 ns | 0.4406 | 0.0057 | - | 3688 B |
|
||||
| Remove_ImmSortedDict | 100 | 8 | 2,031.56 ns | 0.3777 | - | - | 3168 B |
|
||||
| Remove_ExtMap | 100 | 8 | 2,274.33 ns | 0.5798 | - | - | 4856 B |
|
||||
| Remove_ExtHashMap | 100 | 8 | 817.02 ns | 0.5102 | 0.0019 | - | 4272 B |
|
||||
| Build_TransientMap_Standard | 100 | 50 | 43,518.39 ns | 0.7324 | - | - | 6216 B |
|
||||
| Build_TransientMap_Unicode | 100 | 50 | 12,061.37 ns | 0.8850 | 0.0153 | - | 7528 B |
|
||||
| Build_ImmDict | 100 | 50 | 16,497.93 ns | 5.4016 | 0.0610 | - | 45216 B |
|
||||
| Build_ImmSortedDict | 100 | 50 | 22,820.20 ns | 4.2114 | 0.0305 | - | 35232 B |
|
||||
| Build_ExtMap | 100 | 50 | 22,925.57 ns | 5.6458 | 0.0610 | - | 47328 B |
|
||||
| Build_ExtHashMap | 100 | 50 | 13,019.93 ns | 4.3945 | 0.0305 | - | 36776 B |
|
||||
| Retrieve_ImmDict | 100 | 50 | 286.77 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 100 | 50 | 4,620.16 ns | 0.0534 | - | - | 480 B |
|
||||
| Retrieve_PersistentMap_Unicode | 100 | 50 | 158.67 ns | - | - | - | - |
|
||||
| Update_ImmDict | 100 | 50 | 1,490.11 ns | 0.4902 | 0.0019 | - | 4112 B |
|
||||
| Update_PersistentMap_Standard | 100 | 50 | 7,007.05 ns | 2.4033 | 0.0381 | - | 20160 B |
|
||||
| Update_PersistentMap_Unicode | 100 | 50 | 1,976.44 ns | 2.7046 | 0.0534 | - | 22640 B |
|
||||
| Update_TransientMap_Standard | 100 | 50 | 6,208.54 ns | 0.4349 | - | - | 3640 B |
|
||||
| Update_TransientMap_Unicode | 100 | 50 | 665.76 ns | 0.4644 | 0.0057 | - | 3888 B |
|
||||
| Update_ImmSortedDict | 100 | 50 | 2,133.72 ns | 0.4120 | - | - | 3456 B |
|
||||
| Update_ExtMap | 100 | 50 | 1,842.17 ns | 0.4253 | - | - | 3568 B |
|
||||
| Update_ExtHashMap | 100 | 50 | 971.20 ns | 0.5474 | 0.0019 | - | 4592 B |
|
||||
| UpdateSet_ImmDict | 100 | 50 | 1,816.66 ns | 0.6046 | 0.0019 | - | 5072 B |
|
||||
| UpdateSet_PersistentMap_Standard | 100 | 50 | 6,616.38 ns | 2.4033 | 0.0381 | - | 20160 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 100 | 50 | 2,517.24 ns | 2.7046 | 0.0496 | - | 22640 B |
|
||||
| UpdateSet_TransientMap_Standard | 100 | 50 | 5,282.92 ns | 0.4349 | - | - | 3640 B |
|
||||
| UpdateSet_TransientMap_Unicode | 100 | 50 | 1,214.38 ns | 0.4635 | 0.0057 | - | 3888 B |
|
||||
| UpdateSet_ImmSortedDict | 100 | 50 | 2,531.36 ns | 0.4730 | - | - | 3984 B |
|
||||
| UpdateSet_ExtMap | 100 | 50 | 2,399.90 ns | 0.5913 | - | - | 4968 B |
|
||||
| UpdateSet_ExtHashMap | 100 | 50 | 1,041.57 ns | 0.5512 | 0.0019 | - | 4624 B |
|
||||
| Iterate_ImmDict | 100 | 50 | 1,265.60 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap_Standard | 100 | 50 | 183.51 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 100 | 50 | 487.68 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 100 | 50 | 313.58 ns | 0.0038 | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 100 | 50 | 1,174.73 ns | 0.2747 | - | - | 2304 B |
|
||||
| Iterate_PersistentMap_Unicode | 100 | 50 | 190.36 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 100 | 50 | 1,566.27 ns | 0.5283 | 0.0019 | - | 4432 B |
|
||||
| Remove_PersistentMap_Standard | 100 | 50 | 7,539.09 ns | 2.4338 | 0.0381 | - | 20400 B |
|
||||
| Remove_PersistentMap_Unicode | 100 | 50 | 2,572.60 ns | 2.6779 | 0.0458 | - | 22400 B |
|
||||
| Remove_TransientMap_Standard | 100 | 50 | 6,450.13 ns | 0.4654 | - | - | 3928 B |
|
||||
| Remove_TransientMap_Unicode | 100 | 50 | 1,408.78 ns | 0.4406 | 0.0057 | - | 3688 B |
|
||||
| Remove_ImmSortedDict | 100 | 50 | 2,241.88 ns | 0.3891 | - | - | 3264 B |
|
||||
| Remove_ExtMap | 100 | 50 | 2,156.69 ns | 0.4387 | - | - | 3680 B |
|
||||
| Remove_ExtHashMap | 100 | 50 | 927.47 ns | 0.4807 | 0.0010 | - | 4024 B |
|
||||
| Build_TransientMap_Standard | 1000 | 8 | 741,046.07 ns | 5.8594 | - | - | 49512 B |
|
||||
| Build_TransientMap_Unicode | 1000 | 8 | 176,282.35 ns | 7.3242 | 0.7324 | - | 62248 B |
|
||||
| Build_ImmDict | 1000 | 8 | 251,462.15 ns | 79.1016 | 8.3008 | - | 663488 B |
|
||||
| Build_ImmSortedDict | 1000 | 8 | 420,903.44 ns | 61.0352 | 4.8828 | - | 513312 B |
|
||||
| Build_ExtMap | 1000 | 8 | 426,942.00 ns | 79.1016 | 7.3242 | - | 662448 B |
|
||||
| Build_ExtHashMap | 1000 | 8 | 154,509.50 ns | 70.3125 | 4.8828 | - | 589264 B |
|
||||
| Retrieve_ImmDict | 1000 | 8 | 1,057.94 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 1000 | 8 | 71,081.84 ns | 0.4883 | - | - | 4800 B |
|
||||
| Retrieve_PersistentMap_Unicode | 1000 | 8 | 2,056.30 ns | - | - | - | - |
|
||||
| Update_ImmDict | 1000 | 8 | 20,613.44 ns | 7.4768 | 0.2747 | - | 62688 B |
|
||||
| Update_PersistentMap_Standard | 1000 | 8 | 91,445.80 ns | 24.0479 | 2.3193 | - | 201600 B |
|
||||
| Update_PersistentMap_Unicode | 1000 | 8 | 29,005.34 ns | 27.0386 | 2.5330 | - | 226400 B |
|
||||
| Update_TransientMap_Standard | 1000 | 8 | 85,352.16 ns | 4.5166 | 0.4883 | - | 38184 B |
|
||||
| Update_TransientMap_Unicode | 1000 | 8 | 7,968.40 ns | 4.4250 | 0.4425 | - | 37024 B |
|
||||
| Update_ImmSortedDict | 1000 | 8 | 32,334.14 ns | 5.7373 | 0.1221 | - | 48288 B |
|
||||
| Update_ExtMap | 1000 | 8 | 34,280.75 ns | 6.4087 | 0.1831 | - | 53824 B |
|
||||
| Update_ExtHashMap | 1000 | 8 | 11,163.54 ns | 7.1716 | 0.2289 | - | 60016 B |
|
||||
| UpdateSet_ImmDict | 1000 | 8 | 23,570.66 ns | 8.3618 | 0.3967 | - | 69984 B |
|
||||
| UpdateSet_PersistentMap_Standard | 1000 | 8 | 103,655.77 ns | 24.0479 | 2.1973 | - | 201600 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 1000 | 8 | 33,858.71 ns | 27.0386 | 2.6245 | - | 226400 B |
|
||||
| UpdateSet_TransientMap_Standard | 1000 | 8 | 86,914.93 ns | 4.1504 | 0.3662 | - | 35368 B |
|
||||
| UpdateSet_TransientMap_Unicode | 1000 | 8 | 12,939.69 ns | 4.5929 | 0.4883 | - | 38432 B |
|
||||
| UpdateSet_ImmSortedDict | 1000 | 8 | 37,789.08 ns | 6.3477 | 0.2441 | - | 53232 B |
|
||||
| UpdateSet_ExtMap | 1000 | 8 | 38,304.46 ns | 7.6904 | 0.3052 | - | 64576 B |
|
||||
| UpdateSet_ExtHashMap | 1000 | 8 | 14,695.01 ns | 7.7057 | 0.2747 | - | 64464 B |
|
||||
| Iterate_ImmDict | 1000 | 8 | 13,240.07 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap_Standard | 1000 | 8 | 1,628.88 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 1000 | 8 | 4,933.29 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 1000 | 8 | 3,191.58 ns | 0.0038 | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 1000 | 8 | 14,380.64 ns | 2.6550 | - | - | 22320 B |
|
||||
| Iterate_PersistentMap_Unicode | 1000 | 8 | 1,647.93 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 1000 | 8 | 20,527.32 ns | 7.6599 | 0.3052 | - | 64160 B |
|
||||
| Remove_PersistentMap_Standard | 1000 | 8 | 106,140.73 ns | 24.2920 | 1.5869 | - | 204000 B |
|
||||
| Remove_PersistentMap_Unicode | 1000 | 8 | 30,242.16 ns | 26.7639 | 2.4414 | - | 224000 B |
|
||||
| Remove_TransientMap_Standard | 1000 | 8 | 91,057.33 ns | 4.6387 | 0.4883 | - | 39224 B |
|
||||
| Remove_TransientMap_Unicode | 1000 | 8 | 14,218.28 ns | 3.7994 | 0.3662 | - | 31848 B |
|
||||
| Remove_ImmSortedDict | 1000 | 8 | 33,413.24 ns | 5.6763 | 0.1221 | - | 47616 B |
|
||||
| Remove_ExtMap | 1000 | 8 | 37,472.01 ns | 6.8970 | 0.1831 | - | 57744 B |
|
||||
| Remove_ExtHashMap | 1000 | 8 | 12,185.93 ns | 7.9803 | 0.2441 | - | 66864 B |
|
||||
| Build_TransientMap_Standard | 1000 | 50 | 727,350.80 ns | 4.8828 | - | - | 44992 B |
|
||||
| Build_TransientMap_Unicode | 1000 | 50 | 176,948.61 ns | 7.0801 | 0.4883 | - | 59368 B |
|
||||
| Build_ImmDict | 1000 | 50 | 285,938.59 ns | 78.6133 | 8.3008 | - | 660096 B |
|
||||
| Build_ImmSortedDict | 1000 | 50 | 433,882.40 ns | 61.0352 | 5.3711 | - | 511344 B |
|
||||
| Build_ExtMap | 1000 | 50 | 412,922.58 ns | 78.1250 | 7.3242 | - | 656288 B |
|
||||
| Build_ExtHashMap | 1000 | 50 | 185,309.94 ns | 70.3125 | 4.8828 | - | 589128 B |
|
||||
| Retrieve_ImmDict | 1000 | 50 | 3,151.77 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 1000 | 50 | 76,705.78 ns | 0.4883 | - | - | 4800 B |
|
||||
| Retrieve_PersistentMap_Unicode | 1000 | 50 | 2,077.37 ns | - | - | - | - |
|
||||
| Update_ImmDict | 1000 | 50 | 23,312.29 ns | 7.5073 | 0.3052 | - | 62880 B |
|
||||
| Update_PersistentMap_Standard | 1000 | 50 | 93,945.90 ns | 24.0479 | 2.0752 | - | 201600 B |
|
||||
| Update_PersistentMap_Unicode | 1000 | 50 | 29,787.05 ns | 27.0386 | 2.2888 | - | 226400 B |
|
||||
| Update_TransientMap_Standard | 1000 | 50 | 85,349.42 ns | 3.7842 | 0.2441 | - | 32552 B |
|
||||
| Update_TransientMap_Unicode | 1000 | 50 | 7,349.19 ns | 4.0894 | 0.4044 | - | 34208 B |
|
||||
| Update_ImmSortedDict | 1000 | 50 | 32,705.50 ns | 5.6763 | 0.1221 | - | 47952 B |
|
||||
| Update_ExtMap | 1000 | 50 | 33,742.93 ns | 6.5308 | 0.1831 | - | 54720 B |
|
||||
| Update_ExtHashMap | 1000 | 50 | 13,720.77 ns | 7.2479 | 0.2289 | - | 60688 B |
|
||||
| UpdateSet_ImmDict | 1000 | 50 | 24,725.01 ns | 8.0566 | 0.3967 | - | 67424 B |
|
||||
| UpdateSet_PersistentMap_Standard | 1000 | 50 | 106,965.60 ns | 24.1699 | 1.9531 | - | 202504 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 1000 | 50 | 34,398.72 ns | 27.2217 | 2.6245 | - | 227840 B |
|
||||
| UpdateSet_TransientMap_Standard | 1000 | 50 | 89,942.92 ns | 3.9063 | 0.3662 | - | 33456 B |
|
||||
| UpdateSet_TransientMap_Unicode | 1000 | 50 | 13,555.52 ns | 4.4250 | 0.4578 | - | 37056 B |
|
||||
| UpdateSet_ImmSortedDict | 1000 | 50 | 37,035.94 ns | 6.2866 | 0.1831 | - | 53088 B |
|
||||
| UpdateSet_ExtMap | 1000 | 50 | 39,025.94 ns | 7.8735 | 0.3052 | - | 66200 B |
|
||||
| UpdateSet_ExtHashMap | 1000 | 50 | 15,654.51 ns | 7.5989 | 0.2441 | - | 63608 B |
|
||||
| Iterate_ImmDict | 1000 | 50 | 12,858.73 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap_Standard | 1000 | 50 | 1,674.98 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 1000 | 50 | 4,882.38 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 1000 | 50 | 3,498.40 ns | 0.0038 | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 1000 | 50 | 14,585.65 ns | 2.7008 | - | - | 22608 B |
|
||||
| Iterate_PersistentMap_Unicode | 1000 | 50 | 1,654.17 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 1000 | 50 | 24,001.50 ns | 7.6904 | 0.3052 | - | 64480 B |
|
||||
| Remove_PersistentMap_Standard | 1000 | 50 | 115,583.75 ns | 24.2920 | 1.7090 | - | 204000 B |
|
||||
| Remove_PersistentMap_Unicode | 1000 | 50 | 30,177.90 ns | 26.7639 | 2.2583 | - | 224000 B |
|
||||
| Remove_TransientMap_Standard | 1000 | 50 | 104,629.87 ns | 3.9063 | 0.2441 | - | 33592 B |
|
||||
| Remove_TransientMap_Unicode | 1000 | 50 | 13,859.15 ns | 3.2959 | 0.2899 | - | 27624 B |
|
||||
| Remove_ImmSortedDict | 1000 | 50 | 34,305.81 ns | 5.6763 | 0.1221 | - | 47664 B |
|
||||
| Remove_ExtMap | 1000 | 50 | 36,962.41 ns | 6.9580 | 0.2441 | - | 58640 B |
|
||||
| Remove_ExtHashMap | 1000 | 50 | 14,045.01 ns | 7.8583 | 0.1984 | - | 65800 B |
|
||||
| Build_TransientMap_Standard | 10000 | 8 | 10,481,091.44 ns | 46.8750 | 15.6250 | - | 453272 B |
|
||||
| Build_TransientMap_Unicode | 10000 | 8 | 2,288,777.17 ns | 66.4063 | 19.5313 | - | 576584 B |
|
||||
| Build_ImmDict | 10000 | 8 | 4,451,716.58 ns | 1046.8750 | 500.0000 | - | 8790528 B |
|
||||
| Build_ImmSortedDict | 10000 | 8 | 6,589,276.41 ns | 804.6875 | 281.2500 | - | 6767232 B |
|
||||
| Build_ExtMap | 10000 | 8 | 6,466,222.77 ns | 1015.6250 | 437.5000 | - | 8542480 B |
|
||||
| Build_ExtHashMap | 10000 | 8 | 1,987,067.22 ns | 945.3125 | 312.5000 | - | 7921664 B |
|
||||
| Retrieve_ImmDict | 10000 | 8 | 23,653.66 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 10000 | 8 | 1,067,006.38 ns | 7.8125 | - | - | 72000 B |
|
||||
| Retrieve_PersistentMap_Unicode | 10000 | 8 | 33,794.37 ns | - | - | - | - |
|
||||
| Update_ImmDict | 10000 | 8 | 393,904.16 ns | 100.5859 | 25.3906 | - | 844992 B |
|
||||
| Update_PersistentMap_Standard | 10000 | 8 | 1,373,414.67 ns | 304.6875 | 132.8125 | - | 2560000 B |
|
||||
| Update_PersistentMap_Unicode | 10000 | 8 | 416,266.85 ns | 366.2109 | 160.1563 | - | 3064000 B |
|
||||
| Update_TransientMap_Standard | 10000 | 8 | 1,160,717.93 ns | 41.0156 | 13.6719 | - | 346280 B |
|
||||
| Update_TransientMap_Unicode | 10000 | 8 | 154,306.58 ns | 40.7715 | 16.6016 | - | 341792 B |
|
||||
| Update_ImmSortedDict | 10000 | 8 | 583,839.85 ns | 76.1719 | 15.6250 | - | 640368 B |
|
||||
| Update_ExtMap | 10000 | 8 | 571,924.12 ns | 87.8906 | 22.4609 | - | 735640 B |
|
||||
| Update_ExtHashMap | 10000 | 8 | 164,331.80 ns | 102.7832 | 22.9492 | - | 860152 B |
|
||||
| UpdateSet_ImmDict | 10000 | 8 | 433,669.48 ns | 108.3984 | 29.2969 | - | 907072 B |
|
||||
| UpdateSet_PersistentMap_Standard | 10000 | 8 | 1,426,063.43 ns | 306.6406 | 130.8594 | - | 2572328 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 10000 | 8 | 502,233.19 ns | 368.1641 | 154.2969 | - | 3086432 B |
|
||||
| UpdateSet_TransientMap_Standard | 10000 | 8 | 1,183,758.86 ns | 41.0156 | 15.6250 | - | 357200 B |
|
||||
| UpdateSet_TransientMap_Unicode | 10000 | 8 | 224,362.69 ns | 43.7012 | 16.3574 | - | 365632 B |
|
||||
| UpdateSet_ImmSortedDict | 10000 | 8 | 648,222.74 ns | 82.0313 | 19.5313 | - | 687648 B |
|
||||
| UpdateSet_ExtMap | 10000 | 8 | 638,998.68 ns | 99.6094 | 25.3906 | - | 833752 B |
|
||||
| UpdateSet_ExtHashMap | 10000 | 8 | 188,297.97 ns | 104.9805 | 23.9258 | - | 878640 B |
|
||||
| Iterate_ImmDict | 10000 | 8 | 180,912.56 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap_Standard | 10000 | 8 | 17,134.49 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 10000 | 8 | 55,355.92 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 10000 | 8 | 66,171.51 ns | - | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 10000 | 8 | 173,270.08 ns | 20.2637 | - | - | 171360 B |
|
||||
| Iterate_PersistentMap_Unicode | 10000 | 8 | 17,291.12 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 10000 | 8 | 398,833.85 ns | 102.0508 | 24.9023 | - | 857216 B |
|
||||
| Remove_PersistentMap_Standard | 10000 | 8 | 1,488,081.64 ns | 310.5469 | 123.0469 | - | 2608000 B |
|
||||
| Remove_PersistentMap_Unicode | 10000 | 8 | 492,391.59 ns | 363.2813 | 180.6641 | - | 3041408 B |
|
||||
| Remove_TransientMap_Standard | 10000 | 8 | 1,334,741.70 ns | 44.9219 | 15.6250 | - | 388696 B |
|
||||
| Remove_TransientMap_Unicode | 10000 | 8 | 224,694.52 ns | 38.0859 | 15.1367 | - | 319240 B |
|
||||
| Remove_ImmSortedDict | 10000 | 8 | 624,219.05 ns | 77.1484 | 15.6250 | - | 652944 B |
|
||||
| Remove_ExtMap | 10000 | 8 | 648,812.07 ns | 91.7969 | 20.5078 | - | 774000 B |
|
||||
| Remove_ExtHashMap | 10000 | 8 | 187,697.63 ns | 104.2480 | 21.4844 | - | 873600 B |
|
||||
| Build_TransientMap_Standard | 10000 | 50 | 11,121,008.03 ns | 46.8750 | 15.6250 | - | 453272 B |
|
||||
| Build_TransientMap_Unicode | 10000 | 50 | 2,345,447.27 ns | 66.4063 | 23.4375 | - | 567112 B |
|
||||
| Build_ImmDict | 10000 | 50 | 4,711,900.43 ns | 1039.0625 | 507.8125 | - | 8746752 B |
|
||||
| Build_ImmSortedDict | 10000 | 50 | 6,774,329.88 ns | 804.6875 | 265.6250 | - | 6751584 B |
|
||||
| Build_ExtMap | 10000 | 50 | 6,748,429.60 ns | 1015.6250 | 429.6875 | - | 8544720 B |
|
||||
| Build_ExtHashMap | 10000 | 50 | 2,211,770.12 ns | 941.4063 | 332.0313 | - | 7894224 B |
|
||||
| Retrieve_ImmDict | 10000 | 50 | 53,674.39 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 10000 | 50 | 1,099,621.23 ns | 7.8125 | - | - | 72000 B |
|
||||
| Retrieve_PersistentMap_Unicode | 10000 | 50 | 34,282.21 ns | - | - | - | - |
|
||||
| Update_ImmDict | 10000 | 50 | 416,110.39 ns | 101.0742 | 24.9023 | - | 845696 B |
|
||||
| Update_PersistentMap_Standard | 10000 | 50 | 1,432,050.69 ns | 304.6875 | 125.0000 | - | 2560000 B |
|
||||
| Update_PersistentMap_Unicode | 10000 | 50 | 482,376.79 ns | 366.2109 | 158.2031 | - | 3064000 B |
|
||||
| Update_TransientMap_Standard | 10000 | 50 | 1,185,346.37 ns | 39.0625 | 11.7188 | - | 339240 B |
|
||||
| Update_TransientMap_Unicode | 10000 | 50 | 153,479.49 ns | 39.5508 | 13.9160 | - | 331136 B |
|
||||
| Update_ImmSortedDict | 10000 | 50 | 578,927.03 ns | 76.1719 | 14.6484 | - | 641616 B |
|
||||
| Update_ExtMap | 10000 | 50 | 582,689.89 ns | 86.9141 | 20.5078 | - | 732896 B |
|
||||
| Update_ExtHashMap | 10000 | 50 | 195,769.80 ns | 102.5391 | 20.5078 | - | 859328 B |
|
||||
| UpdateSet_ImmDict | 10000 | 50 | 460,644.35 ns | 109.3750 | 30.2734 | - | 915264 B |
|
||||
| UpdateSet_PersistentMap_Standard | 10000 | 50 | 1,488,123.25 ns | 306.6406 | 132.8125 | - | 2575040 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 10000 | 50 | 516,710.59 ns | 368.1641 | 152.3438 | - | 3085600 B |
|
||||
| UpdateSet_TransientMap_Standard | 10000 | 50 | 1,257,184.91 ns | 41.0156 | 13.6719 | - | 355688 B |
|
||||
| UpdateSet_TransientMap_Unicode | 10000 | 50 | 222,602.17 ns | 42.2363 | 15.8691 | - | 354144 B |
|
||||
| UpdateSet_ImmSortedDict | 10000 | 50 | 659,407.65 ns | 82.0313 | 19.5313 | - | 691728 B |
|
||||
| UpdateSet_ExtMap | 10000 | 50 | 652,772.00 ns | 98.6328 | 23.4375 | - | 829552 B |
|
||||
| UpdateSet_ExtHashMap | 10000 | 50 | 216,637.22 ns | 104.9805 | 24.1699 | - | 878840 B |
|
||||
| Iterate_ImmDict | 10000 | 50 | 172,211.95 ns | - | - | - | - |
|
||||
| Iterate_PersistentMap_Standard | 10000 | 50 | 17,227.48 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 10000 | 50 | 56,105.18 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 10000 | 50 | 70,322.76 ns | - | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 10000 | 50 | 176,803.08 ns | 20.0195 | - | - | 168264 B |
|
||||
| Iterate_PersistentMap_Unicode | 10000 | 50 | 17,473.52 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 10000 | 50 | 429,520.72 ns | 102.5391 | 28.3203 | - | 860032 B |
|
||||
| Remove_PersistentMap_Standard | 10000 | 50 | 1,558,056.72 ns | 310.5469 | 125.0000 | - | 2610816 B |
|
||||
| Remove_PersistentMap_Unicode | 10000 | 50 | 522,677.95 ns | 363.2813 | 158.2031 | - | 3042816 B |
|
||||
| Remove_TransientMap_Standard | 10000 | 50 | 1,333,105.09 ns | 44.9219 | 15.6250 | - | 391512 B |
|
||||
| Remove_TransientMap_Unicode | 10000 | 50 | 223,026.23 ns | 37.3535 | 13.1836 | - | 312808 B |
|
||||
| Remove_ImmSortedDict | 10000 | 50 | 611,137.62 ns | 77.1484 | 15.6250 | - | 651840 B |
|
||||
| Remove_ExtMap | 10000 | 50 | 630,117.55 ns | 91.7969 | 21.4844 | - | 771256 B |
|
||||
| Remove_ExtHashMap | 10000 | 50 | 208,051.51 ns | 104.7363 | 20.7520 | - | 877216 B |
|
||||
| Build_TransientMap_Standard | 100000 | 8 | 158,074,169.00 ns | 500.0000 | 250.0000 | - | 4524208 B |
|
||||
| Build_TransientMap_Unicode | 100000 | 8 | 40,354,289.36 ns | 692.3077 | 538.4615 | - | 5798728 B |
|
||||
| Build_ImmDict | 100000 | 8 | 95,840,918.80 ns | 13200.0000 | 4600.0000 | 200.0000 | 109352965 B |
|
||||
| Build_ImmSortedDict | 100000 | 8 | 145,451,097.25 ns | 10000.0000 | 3750.0000 | - | 83946432 B |
|
||||
| Build_ExtMap | 100000 | 8 | 121,026,729.25 ns | 12500.0000 | 5750.0000 | - | 104660688 B |
|
||||
| Build_ExtHashMap | 100000 | 8 | 45,582,232.06 ns | 12000.0000 | 2583.3333 | 83.3333 | 99799303 B |
|
||||
| Retrieve_ImmDict | 100000 | 8 | 1,518,946.53 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 100000 | 8 | 15,954,066.23 ns | 93.7500 | - | - | 960000 B |
|
||||
| Retrieve_PersistentMap_Unicode | 100000 | 8 | 1,344,373.24 ns | - | - | - | - |
|
||||
| Update_ImmDict | 100000 | 8 | 9,168,537.50 ns | 1265.6250 | 1062.5000 | - | 10588288 B |
|
||||
| Update_PersistentMap_Standard | 100000 | 8 | 24,537,720.52 ns | 3718.7500 | 2906.2500 | 31.2500 | 31040034 B |
|
||||
| Update_PersistentMap_Unicode | 100000 | 8 | 11,821,730.59 ns | 4671.8750 | 2500.0000 | 62.5000 | 38640073 B |
|
||||
| Update_TransientMap_Standard | 100000 | 8 | 18,410,364.31 ns | 406.2500 | 312.5000 | - | 3415688 B |
|
||||
| Update_TransientMap_Unicode | 100000 | 8 | 3,724,912.85 ns | 414.0625 | 351.5625 | - | 3487208 B |
|
||||
| Update_ImmSortedDict | 100000 | 8 | 11,002,667.72 ns | 953.1250 | 781.2500 | - | 8020080 B |
|
||||
| Update_ExtMap | 100000 | 8 | 11,289,391.77 ns | 1093.7500 | 937.5000 | - | 9246400 B |
|
||||
| Update_ExtHashMap | 100000 | 8 | 3,795,553.44 ns | 1265.6250 | 968.7500 | - | 10624032 B |
|
||||
| UpdateSet_ImmDict | 100000 | 8 | 9,324,688.19 ns | 1343.7500 | 1046.8750 | - | 11264256 B |
|
||||
| UpdateSet_PersistentMap_Standard | 100000 | 8 | 28,227,498.72 ns | 3750.0000 | 2593.7500 | 31.2500 | 31143252 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 100000 | 8 | 12,980,484.49 ns | 4703.1250 | 2250.0000 | 62.5000 | 38809434 B |
|
||||
| UpdateSet_TransientMap_Standard | 100000 | 8 | 18,455,887.50 ns | 406.2500 | 312.5000 | - | 3514688 B |
|
||||
| UpdateSet_TransientMap_Unicode | 100000 | 8 | 4,599,394.95 ns | 429.6875 | 382.8125 | - | 3636872 B |
|
||||
| UpdateSet_ImmSortedDict | 100000 | 8 | 11,611,425.71 ns | 1015.6250 | 875.0000 | - | 8495856 B |
|
||||
| UpdateSet_ExtMap | 100000 | 8 | 13,256,339.44 ns | 1218.7500 | 1015.6250 | - | 10287328 B |
|
||||
| UpdateSet_ExtHashMap | 100000 | 8 | 4,056,833.05 ns | 1281.2500 | 992.1875 | - | 10752664 B |
|
||||
| Iterate_ImmDict | 100000 | 8 | 2,245,167.14 ns | - | - | - | 96 B |
|
||||
| Iterate_PersistentMap_Standard | 100000 | 8 | 226,078.34 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 100000 | 8 | 752,117.01 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 100000 | 8 | 1,153,701.70 ns | - | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 100000 | 8 | 2,747,144.18 ns | 273.4375 | - | - | 2318432 B |
|
||||
| Iterate_PersistentMap_Unicode | 100000 | 8 | 241,814.65 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 100000 | 8 | 8,922,500.09 ns | 1281.2500 | 1031.2500 | - | 10744896 B |
|
||||
| Remove_PersistentMap_Standard | 100000 | 8 | 28,366,797.02 ns | 3812.5000 | 2750.0000 | 31.2500 | 31781143 B |
|
||||
| Remove_PersistentMap_Unicode | 100000 | 8 | 12,499,542.97 ns | 4640.6250 | 2875.0000 | 62.5000 | 38412730 B |
|
||||
| Remove_TransientMap_Standard | 100000 | 8 | 21,085,953.04 ns | 468.7500 | 343.7500 | - | 4134328 B |
|
||||
| Remove_TransientMap_Unicode | 100000 | 8 | 3,227,138.07 ns | 382.8125 | 324.2188 | - | 3226088 B |
|
||||
| Remove_ImmSortedDict | 100000 | 8 | 10,475,303.83 ns | 953.1250 | 781.2500 | - | 8031888 B |
|
||||
| Remove_ExtMap | 100000 | 8 | 11,473,612.68 ns | 1140.6250 | 937.5000 | - | 9630448 B |
|
||||
| Remove_ExtHashMap | 100000 | 8 | 3,920,078.08 ns | 1265.6250 | 914.0625 | - | 10624344 B |
|
||||
| Build_TransientMap_Standard | 100000 | 50 | 186,991,900.50 ns | 500.0000 | - | - | 4511632 B |
|
||||
| Build_TransientMap_Unicode | 100000 | 50 | 42,267,081.83 ns | 666.6667 | 500.0000 | - | 5763784 B |
|
||||
| Build_ImmDict | 100000 | 50 | 91,363,990.94 ns | 13166.6667 | 4833.3333 | 166.6667 | 109457840 B |
|
||||
| Build_ImmSortedDict | 100000 | 50 | 134,058,261.08 ns | 10000.0000 | 4000.0000 | - | 83731632 B |
|
||||
| Build_ExtMap | 100000 | 50 | 130,083,384.42 ns | 12250.0000 | 5250.0000 | - | 104371560 B |
|
||||
| Build_ExtHashMap | 100000 | 50 | 50,176,040.06 ns | 11909.0909 | 2545.4545 | - | 99721040 B |
|
||||
| Retrieve_ImmDict | 100000 | 50 | 1,878,376.76 ns | - | - | - | - |
|
||||
| Retrieve_PersistentMap_Standard | 100000 | 50 | 22,662,880.57 ns | 93.7500 | - | - | 960000 B |
|
||||
| Retrieve_PersistentMap_Unicode | 100000 | 50 | 1,421,803.13 ns | - | - | - | - |
|
||||
| Update_ImmDict | 100000 | 50 | 8,957,341.32 ns | 1265.6250 | 1062.5000 | - | 10646144 B |
|
||||
| Update_PersistentMap_Standard | 100000 | 50 | 37,471,664.44 ns | 3692.3077 | 2769.2308 | - | 31040000 B |
|
||||
| Update_PersistentMap_Unicode | 100000 | 50 | 12,856,950.49 ns | 4656.2500 | 2781.2500 | 46.8750 | 38640047 B |
|
||||
| Update_TransientMap_Standard | 100000 | 50 | 26,563,266.79 ns | 406.2500 | 312.5000 | - | 3414056 B |
|
||||
| Update_TransientMap_Unicode | 100000 | 50 | 4,173,539.70 ns | 406.2500 | 335.9375 | - | 3440328 B |
|
||||
| Update_ImmSortedDict | 100000 | 50 | 11,812,577.59 ns | 953.1250 | 781.2500 | - | 8012928 B |
|
||||
| Update_ExtMap | 100000 | 50 | 11,842,757.42 ns | 1093.7500 | 906.2500 | - | 9233632 B |
|
||||
| Update_ExtHashMap | 100000 | 50 | 4,189,884.01 ns | 1265.6250 | 984.3750 | - | 10632736 B |
|
||||
| UpdateSet_ImmDict | 100000 | 50 | 10,459,268.37 ns | 1343.7500 | 1031.2500 | - | 11256960 B |
|
||||
| UpdateSet_PersistentMap_Standard | 100000 | 50 | 41,029,434.25 ns | 3666.6667 | 2500.0000 | - | 31141992 B |
|
||||
| UpdateSet_PersistentMap_Unicode | 100000 | 50 | 14,028,512.79 ns | 4687.5000 | 2421.8750 | 46.8750 | 38835953 B |
|
||||
| UpdateSet_TransientMap_Standard | 100000 | 50 | 28,314,403.39 ns | 406.2500 | 312.5000 | - | 3509008 B |
|
||||
| UpdateSet_TransientMap_Unicode | 100000 | 50 | 5,267,848.18 ns | 429.6875 | 359.3750 | - | 3630600 B |
|
||||
| UpdateSet_ImmSortedDict | 100000 | 50 | 14,089,168.54 ns | 1015.6250 | 890.6250 | - | 8501136 B |
|
||||
| UpdateSet_ExtMap | 100000 | 50 | 12,785,268.31 ns | 1218.7500 | 1015.6250 | - | 10265544 B |
|
||||
| UpdateSet_ExtHashMap | 100000 | 50 | 4,418,089.19 ns | 1281.2500 | 992.1875 | - | 10750000 B |
|
||||
| Iterate_ImmDict | 100000 | 50 | 2,338,660.26 ns | - | - | - | 384 B |
|
||||
| Iterate_PersistentMap_Standard | 100000 | 50 | 211,359.08 ns | - | - | - | - |
|
||||
| Iterate_ImmSortedDict | 100000 | 50 | 715,600.57 ns | - | - | - | - |
|
||||
| Iterate_ExtMap | 100000 | 50 | 1,117,575.94 ns | - | - | - | 32 B |
|
||||
| Iterate_ExtHashMap | 100000 | 50 | 2,975,826.72 ns | 277.3438 | - | - | 2319904 B |
|
||||
| Iterate_PersistentMap_Unicode | 100000 | 50 | 217,594.04 ns | - | - | - | - |
|
||||
| Remove_ImmDict | 100000 | 50 | 9,190,906.43 ns | 1281.2500 | 1046.8750 | - | 10741824 B |
|
||||
| Remove_PersistentMap_Standard | 100000 | 50 | 42,259,057.45 ns | 3727.2727 | 2636.3636 | - | 31774080 B |
|
||||
| Remove_PersistentMap_Unicode | 100000 | 50 | 11,946,176.86 ns | 4625.0000 | 2859.3750 | 46.8750 | 38402868 B |
|
||||
| Remove_TransientMap_Standard | 100000 | 50 | 26,120,804.07 ns | 468.7500 | 343.7500 | - | 4127064 B |
|
||||
| Remove_TransientMap_Unicode | 100000 | 50 | 3,583,417.50 ns | 382.8125 | 312.5000 | - | 3205960 B |
|
||||
| Remove_ImmSortedDict | 100000 | 50 | 14,164,094.39 ns | 953.1250 | 781.2500 | - | 8032656 B |
|
||||
| Remove_ExtMap | 100000 | 50 | 13,861,593.95 ns | 1140.6250 | 921.8750 | - | 9617232 B |
|
||||
| Remove_ExtHashMap | 100000 | 50 | 4,797,368.31 ns | 1265.6250 | 937.5000 | - | 10628320 B |
|
||||
|
||||
| Method | N | KeyLength | Mean | Gen0 | Gen1 | Allocated |
|
||||
|--------------------------- |------ |---------- |----------------:|---------:|---------:|----------:|
|
||||
| 'Build: NiceBTree' | 10000 | 10 | 2,037,851.45 ns | 35.1563 | 15.6250 | 644600 B |
|
||||
| 'Build: MS HashDict' | 10000 | 10 | 1,647,876.61 ns | 37.1094 | 15.6250 | 640096 B |
|
||||
| 'Build: MS SortedDict' | 10000 | 10 | 3,853,709.48 ns | 31.2500 | 11.7188 | 560112 B |
|
||||
| 'Build: LangExt HashMap' | 10000 | 10 | 1,612,117.07 ns | 472.6563 | 154.2969 | 7919328 B |
|
||||
| 'Build: LangExt Map' | 10000 | 10 | 5,363,298.26 ns | 507.8125 | 203.1250 | 8594784 B |
|
||||
| 'Read: NiceBTree' | 10000 | 10 | 36.30 ns | - | - | - |
|
||||
| 'Read: MS HashDict' | 10000 | 10 | 12.66 ns | - | - | - |
|
||||
| 'Read: MS SortedDict' | 10000 | 10 | 233.59 ns | - | - | - |
|
||||
| 'Read: LangExt HashMap' | 10000 | 10 | 28.61 ns | - | - | - |
|
||||
| 'Read: LangExt Map' | 10000 | 10 | 268.13 ns | - | - | - |
|
||||
| 'Iterate: NiceBTree' | 10000 | 10 | 12,630.95 ns | - | - | - |
|
||||
| 'Iterate: MS HashDict' | 10000 | 10 | 151,314.44 ns | - | - | - |
|
||||
| 'Iterate: MS SortedDict' | 10000 | 10 | 57,402.20 ns | - | - | - |
|
||||
| 'Iterate: LangExt HashMap' | 10000 | 10 | 148,980.47 ns | 10.0098 | - | 170712 B |
|
||||
| 'Iterate: LangExt Map' | 10000 | 10 | 34,428.07 ns | - | - | 32 B |
|
||||
| 'Update: NiceBTree' | 10000 | 10 | 303.01 ns | 0.2027 | 0.0024 | 3392 B |
|
||||
| 'Update: MS HashDict' | 10000 | 10 | 48.36 ns | 0.0100 | - | 168 B |
|
||||
| 'Update: MS SortedDict' | 10000 | 10 | 137.47 ns | 0.0196 | - | 328 B |
|
||||
| 'Update: LangExt HashMap' | 10000 | 10 | 102.57 ns | 0.0502 | 0.0001 | 840 B |
|
||||
| 'Update: LangExt Map' | 10000 | 10 | 122.54 ns | 0.0186 | - | 312 B |
|
||||
| 'Build: NiceBTree' | 10000 | 50 | 2,020,984.87 ns | 35.1563 | 11.7188 | 624248 B |
|
||||
| 'Build: MS HashDict' | 10000 | 50 | 1,811,186.24 ns | 37.1094 | 15.6250 | 640096 B |
|
||||
| 'Build: MS SortedDict' | 10000 | 50 | 3,883,214.25 ns | 31.2500 | 15.6250 | 560112 B |
|
||||
| 'Build: LangExt HashMap' | 10000 | 50 | 1,784,616.64 ns | 472.6563 | 154.2969 | 7926712 B |
|
||||
| 'Build: LangExt Map' | 10000 | 50 | 5,248,030.22 ns | 507.8125 | 203.1250 | 8544720 B |
|
||||
| 'Read: NiceBTree' | 10000 | 50 | 40.64 ns | - | - | - |
|
||||
| 'Read: MS HashDict' | 10000 | 50 | 29.91 ns | - | - | - |
|
||||
| 'Read: MS SortedDict' | 10000 | 50 | 255.55 ns | - | - | - |
|
||||
| 'Read: LangExt HashMap' | 10000 | 50 | 47.61 ns | - | - | - |
|
||||
| 'Read: LangExt Map' | 10000 | 50 | 255.68 ns | - | - | - |
|
||||
| 'Iterate: NiceBTree' | 10000 | 50 | 12,718.71 ns | - | - | - |
|
||||
| 'Iterate: MS HashDict' | 10000 | 50 | 170,815.59 ns | - | - | - |
|
||||
| 'Iterate: MS SortedDict' | 10000 | 50 | 68,982.58 ns | - | - | - |
|
||||
| 'Iterate: LangExt HashMap' | 10000 | 50 | 144,442.27 ns | 9.7656 | - | 165600 B |
|
||||
| 'Iterate: LangExt Map' | 10000 | 50 | 35,082.49 ns | - | - | 32 B |
|
||||
| 'Update: NiceBTree' | 10000 | 50 | 393.56 ns | 0.2027 | 0.0024 | 3392 B |
|
||||
| 'Update: MS HashDict' | 10000 | 50 | 114.57 ns | 0.0215 | - | 360 B |
|
||||
| 'Update: MS SortedDict' | 10000 | 50 | 65.51 ns | 0.0129 | - | 216 B |
|
||||
| 'Update: LangExt HashMap' | 10000 | 50 | 103.28 ns | 0.0535 | - | 896 B |
|
||||
| 'Update: LangExt Map' | 10000 | 50 | 67.62 ns | 0.0119 | - | 200 B |
|
||||
|
||||
#+end_src
|
||||
Architecture Notes: Key Strategies
|
||||
|
||||
|
||||
** Architecture Notes: Key Strategies
|
||||
NiceBtree uses =IKeyStrategy<K>= to map generic keys (like =string= or =double=) into sortable =long= prefixes. This achieves two things:
|
||||
1. Enables AVX512/AVX2 vector instructions to search internal nodes simultaneously.
|
||||
2. Avoids expensive =IComparable<T>= interface calls or =string.Compare= during the initial descent of the tree, only falling back to exact comparisons when refining the search within a leaf.
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
namespace PersistentMap.Tests;
|
||||
using PersistentMap;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
public class StandardStrategy
|
||||
{
|
||||
private static string GenerateRandomString(int length, Random rnd)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return new string(Enumerable.Repeat(chars, length).Select(s => s[rnd.Next(s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Setup()
|
||||
{
|
||||
var N = 1000;
|
||||
var _stdStrategy = new StandardStrategy<string>();
|
||||
var _uniStrategy = new UnicodeStrategy();
|
||||
var rnd = new Random(42);
|
||||
var StringLength = 10;
|
||||
// Build random strings
|
||||
var _allKeys = Enumerable.Range(0, N).Select(_ => GenerateRandomString(StringLength, rnd)).Distinct().ToArray();
|
||||
|
||||
// Regenerate if Distinct() reduced array size (highly unlikely with length 8/50, but safe)
|
||||
while (_allKeys.Length < N)
|
||||
{
|
||||
_allKeys = _allKeys.Concat(new[] { GenerateRandomString(StringLength, rnd) }).Distinct().ToArray();
|
||||
}
|
||||
|
||||
var transStd = BaseOrderedMap<string, int, StandardStrategy<string>>.CreateTransient(_stdStrategy);
|
||||
var transUni = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_uniStrategy);
|
||||
for (int i = 0; i < _allKeys.Length; i++)
|
||||
{
|
||||
transStd.Set(_allKeys[i], i);
|
||||
transUni.Set(_allKeys[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,445 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using LanguageExt;
|
||||
using PersistentMap;
|
||||
|
||||
namespace MapBenchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class StringMapBenchmarks
|
||||
{
|
||||
[Params(100, 1000, 10000, 100000)]
|
||||
public int N { get; set; }
|
||||
|
||||
[Params(8, 50)]
|
||||
public int StringLength { get; set; }
|
||||
|
||||
private string[] _allKeys;
|
||||
private string[] _retrieveKeys;
|
||||
private string[] _updateKeys;
|
||||
private string[] _removeKeys;
|
||||
private string[] _mixedKeys;
|
||||
|
||||
private ImmutableDictionary<string, int> _immDict;
|
||||
private ImmutableSortedDictionary<string, int> _immSortedDict;
|
||||
private LanguageExt.Map<string, int> _extMap;
|
||||
private LanguageExt.HashMap<string, int> _extHashMap;
|
||||
|
||||
private PersistentMap<string, int, StandardStrategy<string>> _persistentMapStandard;
|
||||
private PersistentMap<string, int, UnicodeStrategy> _persistentMapUnicode;
|
||||
|
||||
private readonly StandardStrategy<string> _stdStrategy = new StandardStrategy<string>();
|
||||
private readonly UnicodeStrategy _uniStrategy = new UnicodeStrategy();
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var rnd = new Random(42);
|
||||
|
||||
// Build random strings
|
||||
_allKeys = Enumerable.Range(0, N).Select(_ => GenerateRandomString(StringLength, rnd)).Distinct().ToArray();
|
||||
|
||||
// Regenerate if Distinct() reduced array size (highly unlikely with length 8/50, but safe)
|
||||
while (_allKeys.Length < N)
|
||||
{
|
||||
_allKeys = _allKeys.Concat(new[] { GenerateRandomString(StringLength, rnd) }).Distinct().ToArray();
|
||||
}
|
||||
|
||||
int subsetSize = Math.Max(1, N / 10);
|
||||
|
||||
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();
|
||||
|
||||
var existingHalf = shuffled.Skip(subsetSize * 3).Take(subsetSize / 2).ToArray();
|
||||
var newHalf = Enumerable.Range(0, subsetSize - (subsetSize / 2)).Select(_ => GenerateRandomString(StringLength, rnd)).ToArray();
|
||||
_mixedKeys = existingHalf.Concat(newHalf).OrderBy(x => rnd.Next()).ToArray();
|
||||
|
||||
// Pre-build collections
|
||||
_immDict = ImmutableDictionary.CreateRange(_allKeys.Select((k, i) => new KeyValuePair<string, int>(k, i)));
|
||||
_immSortedDict = ImmutableSortedDictionary.CreateRange(_allKeys.Select((k, i) => new KeyValuePair<string, int>(k, i)));
|
||||
|
||||
_extMap = LanguageExt.Map.empty<string, int>();
|
||||
_extHashMap = LanguageExt.HashMap.empty<string, int>();
|
||||
for (int i = 0; i < _allKeys.Length; i++)
|
||||
{
|
||||
_extMap = _extMap.AddOrUpdate(_allKeys[i], i);
|
||||
_extHashMap = _extHashMap.AddOrUpdate(_allKeys[i], i);
|
||||
}
|
||||
|
||||
var transStd = BaseOrderedMap<string, int, StandardStrategy<string>>.CreateTransient(_stdStrategy);
|
||||
var transUni = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_uniStrategy);
|
||||
for (int i = 0; i < _allKeys.Length; i++)
|
||||
{
|
||||
transStd.Set(_allKeys[i], i);
|
||||
transUni.Set(_allKeys[i], i);
|
||||
}
|
||||
_persistentMapStandard = transStd.ToPersistent();
|
||||
_persistentMapUnicode = transUni.ToPersistent();
|
||||
}
|
||||
|
||||
private static string GenerateRandomString(int length, Random rnd)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return new string(Enumerable.Repeat(chars, length).Select(s => s[rnd.Next(s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
// --- 1. BUILD ---
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, StandardStrategy<string>> Build_TransientMap_Standard()
|
||||
{
|
||||
var map = BaseOrderedMap<string, int, StandardStrategy<string>>.CreateTransient(_stdStrategy);
|
||||
for (int i = 0; i < _allKeys.Length; i++) map.Set(_allKeys[i], i);
|
||||
return map.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, UnicodeStrategy> Build_TransientMap_Unicode()
|
||||
{
|
||||
var map = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_uniStrategy);
|
||||
for (int i = 0; i < _allKeys.Length; i++) map.Set(_allKeys[i], i);
|
||||
return map.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ImmutableDictionary<string, int> Build_ImmDict()
|
||||
{
|
||||
var map = ImmutableDictionary<string, int>.Empty;
|
||||
for (int i = 0; i < _allKeys.Length; i++) map = map.Add(_allKeys[i], i);
|
||||
return map;
|
||||
}
|
||||
|
||||
// --- 1. BUILD (Missing) ---
|
||||
|
||||
[Benchmark]
|
||||
public ImmutableSortedDictionary<string, int> Build_ImmSortedDict()
|
||||
{
|
||||
var map = ImmutableSortedDictionary<string, int>.Empty;
|
||||
for (int i = 0; i < _allKeys.Length; i++) map = map.Add(_allKeys[i], i);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.Map<string, int> Build_ExtMap()
|
||||
{
|
||||
var map = LanguageExt.Map.empty<string, int>();
|
||||
for (int i = 0; i < _allKeys.Length; i++) map = map.AddOrUpdate(_allKeys[i], i);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.HashMap<string, int> Build_ExtHashMap()
|
||||
{
|
||||
var map = LanguageExt.HashMap.empty<string, int>();
|
||||
for (int i = 0; i < _allKeys.Length; i++) map = map.AddOrUpdate(_allKeys[i], i);
|
||||
return map;
|
||||
}
|
||||
|
||||
// --- 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_PersistentMap_Standard()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var k in _retrieveKeys)
|
||||
if (_persistentMapStandard.TryGetValue(k, out _)) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int Retrieve_PersistentMap_Unicode()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var k in _retrieveKeys)
|
||||
if (_persistentMapUnicode.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;
|
||||
}
|
||||
|
||||
|
||||
// --- 3. UPDATING ---
|
||||
|
||||
[Benchmark]
|
||||
public ImmutableDictionary<string, int> Update_ImmDict()
|
||||
{
|
||||
var map = _immDict;
|
||||
foreach (var k in _updateKeys) map = map.SetItem(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, StandardStrategy<string>> Update_PersistentMap_Standard()
|
||||
{
|
||||
var map = _persistentMapStandard;
|
||||
foreach (var k in _updateKeys) map = map.Set(k, 999);
|
||||
return map;
|
||||
}
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, UnicodeStrategy> Update_PersistentMap_Unicode()
|
||||
{
|
||||
var map = _persistentMapUnicode;
|
||||
foreach (var k in _updateKeys) map = map.Set(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, StandardStrategy<string>> Update_TransientMap_Standard()
|
||||
{
|
||||
var transient = _persistentMapStandard.ToTransient();
|
||||
foreach (var k in _updateKeys) transient.Set(k, 999);
|
||||
return transient.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, UnicodeStrategy> Update_TransientMap_Unicode()
|
||||
{
|
||||
var transient = _persistentMapUnicode.ToTransient();
|
||||
foreach (var k in _updateKeys) transient.Set(k, 999);
|
||||
return transient.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ImmutableSortedDictionary<string, int> Update_ImmSortedDict()
|
||||
{
|
||||
var map = _immSortedDict;
|
||||
foreach (var k in _updateKeys) map = map.SetItem(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.Map<string, int> Update_ExtMap()
|
||||
{
|
||||
var map = _extMap;
|
||||
foreach (var k in _updateKeys) map = map.SetItem(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.HashMap<string, 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<string, int> UpdateSet_ImmDict()
|
||||
{
|
||||
var map = _immDict;
|
||||
foreach (var k in _mixedKeys) map = map.SetItem(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, StandardStrategy<string>> UpdateSet_PersistentMap_Standard()
|
||||
{
|
||||
var map = _persistentMapStandard;
|
||||
foreach (var k in _mixedKeys) map = map.Set(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, UnicodeStrategy> UpdateSet_PersistentMap_Unicode()
|
||||
{
|
||||
var map = _persistentMapUnicode;
|
||||
foreach (var k in _mixedKeys) map = map.Set(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, StandardStrategy<string>> UpdateSet_TransientMap_Standard()
|
||||
{
|
||||
var transient = _persistentMapStandard.ToTransient();
|
||||
foreach (var k in _mixedKeys) transient.Set(k, 999);
|
||||
return transient.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, UnicodeStrategy> UpdateSet_TransientMap_Unicode()
|
||||
{
|
||||
var transient = _persistentMapUnicode.ToTransient();
|
||||
foreach (var k in _mixedKeys) transient.Set(k, 999);
|
||||
return transient.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ImmutableSortedDictionary<string, int> UpdateSet_ImmSortedDict()
|
||||
{
|
||||
var map = _immSortedDict;
|
||||
foreach (var k in _mixedKeys) map = map.SetItem(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.Map<string, int> UpdateSet_ExtMap()
|
||||
{
|
||||
var map = _extMap;
|
||||
foreach (var k in _mixedKeys) map = map.AddOrUpdate(k, 999);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.HashMap<string, 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_Standard()
|
||||
{
|
||||
int sum = 0;
|
||||
foreach (var kvp in _persistentMapStandard) 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;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int Iterate_PersistentMap_Unicode()
|
||||
{
|
||||
int sum = 0;
|
||||
foreach (var kvp in _persistentMapUnicode) sum += kvp.Value;
|
||||
return sum;
|
||||
}
|
||||
|
||||
// --- 6. REMOVAL ---
|
||||
|
||||
[Benchmark]
|
||||
public ImmutableDictionary<string, int> Remove_ImmDict()
|
||||
{
|
||||
var map = _immDict;
|
||||
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, StandardStrategy<string>> Remove_PersistentMap_Standard()
|
||||
{
|
||||
var map = _persistentMapStandard;
|
||||
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, UnicodeStrategy> Remove_PersistentMap_Unicode()
|
||||
{
|
||||
var map = _persistentMapUnicode;
|
||||
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, StandardStrategy<string>> Remove_TransientMap_Standard()
|
||||
{
|
||||
var transient = _persistentMapStandard.ToTransient();
|
||||
foreach (var k in _removeKeys) transient.Remove(k);
|
||||
return transient.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public PersistentMap<string, int, UnicodeStrategy> Remove_TransientMap_Unicode()
|
||||
{
|
||||
var transient = _persistentMapUnicode.ToTransient();
|
||||
foreach (var k in _removeKeys) transient.Remove(k);
|
||||
return transient.ToPersistent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ImmutableSortedDictionary<string, int> Remove_ImmSortedDict()
|
||||
{
|
||||
var map = _immSortedDict;
|
||||
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.Map<string, int> Remove_ExtMap()
|
||||
{
|
||||
var map = _extMap;
|
||||
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||
return map;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public LanguageExt.HashMap<string, int> Remove_ExtHashMap()
|
||||
{
|
||||
var map = _extHashMap;
|
||||
foreach (var k in _removeKeys) map = map.Remove(k);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue