Updated benchmarks
fixed prefix logic
This commit is contained in:
parent
f1488881d3
commit
978d0873dc
4 changed files with 67 additions and 246 deletions
|
|
@ -54,7 +54,7 @@ public static Node<K> Set<K, V>(Node<K> root, K key, V value, IKeyStrategy<K> st
|
|||
newRoot.Children[1] = splitResult.NewNode;
|
||||
newRoot.SetCount(1);
|
||||
if (strategy.UsesPrefixes)
|
||||
newRoot._prefixes![0] = strategy.GetPrefix(splitResult.Separator);
|
||||
newRoot.AllPrefixes[0] = strategy.GetPrefix(splitResult.Separator);
|
||||
|
||||
return newRoot;
|
||||
}
|
||||
|
|
@ -195,32 +195,6 @@ where TStrategy : IKeyStrategy<K>
|
|||
|
||||
Span<K> keys = node.GetKeys();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// COMPILE-TIME DISPATCH (INT)
|
||||
// ---------------------------------------------------------
|
||||
if (typeof(K) == typeof(int))
|
||||
{
|
||||
// 1. Get pointer to start of keys
|
||||
ref K startK = ref MemoryMarshal.GetReference(keys);
|
||||
|
||||
// 2. Cast pointer to int (Bypasses 'struct' constraint)
|
||||
ref int startInt = ref Unsafe.As<K, int>(ref startK);
|
||||
|
||||
// 3. Create new Span<int> manually
|
||||
var intKeys = MemoryMarshal.CreateSpan(ref startInt, keys.Length);
|
||||
|
||||
// 4. Run SIMD Search
|
||||
return SearchNumericKeysSIMD(intKeys, Unsafe.As<K, int>(ref key));
|
||||
}
|
||||
else if (typeof(K) == typeof(long))
|
||||
{
|
||||
ref K startK = ref MemoryMarshal.GetReference(keys);
|
||||
ref long startLong = ref Unsafe.As<K, long>(ref startK);
|
||||
var longKeys = MemoryMarshal.CreateSpan(ref startLong, keys.Length);
|
||||
|
||||
return SearchNumericKeysSIMD(longKeys, Unsafe.As<K, long>(ref key));
|
||||
}
|
||||
|
||||
|
||||
return LinearSearchKeys(node.GetKeys(), key, strategy);
|
||||
}
|
||||
|
|
@ -259,25 +233,6 @@ where TStrategy : IKeyStrategy<K>
|
|||
{
|
||||
if (!strategy.UsesPrefixes)
|
||||
{
|
||||
// A. Optimize for INT
|
||||
if (typeof(K) == typeof(int))
|
||||
{
|
||||
ref K startK = ref MemoryMarshal.GetReference(node.GetKeys());
|
||||
ref int startInt = ref Unsafe.As<K, int>(ref startK);
|
||||
var intKeys = MemoryMarshal.CreateSpan(ref startInt, node.GetKeys().Length);
|
||||
|
||||
return SearchNumericRoutingSIMD(intKeys, Unsafe.As<K, int>(ref key));
|
||||
}
|
||||
// B. Optimize for LONG (or Double via bit-casting)
|
||||
else if (typeof(K) == typeof(long))
|
||||
{
|
||||
ref K startK = ref MemoryMarshal.GetReference(node.GetKeys());
|
||||
ref long startLong = ref Unsafe.As<K, long>(ref startK);
|
||||
var longKeys = MemoryMarshal.CreateSpan(ref startLong, node.GetKeys().Length);
|
||||
|
||||
return SearchNumericRoutingSIMD(longKeys, Unsafe.As<K, long>(ref key));
|
||||
}
|
||||
|
||||
// C. Fallback
|
||||
return LinearSearchRouting(node.GetKeys(), key, strategy);
|
||||
}
|
||||
|
|
@ -292,118 +247,6 @@ where TStrategy : IKeyStrategy<K>
|
|||
return RefineRouting(index, node.Keys, node.Header.Count, key, strategy);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int SearchNumericKeysSIMD<T>(Span<T> keys, T key)
|
||||
where T : struct, INumber<T> // Constraints ensure we deal with values
|
||||
{
|
||||
// 1. Vector Setup
|
||||
int len = keys.Length;
|
||||
int vectorSize = Vector<T>.Count;
|
||||
int i = 0;
|
||||
|
||||
// Create a vector of [key, key, key...]
|
||||
Vector<T> vKey = new Vector<T>(key);
|
||||
|
||||
// 2. Main SIMD Loop
|
||||
ref T start = ref MemoryMarshal.GetReference(keys);
|
||||
|
||||
while (i <= len - vectorSize)
|
||||
{
|
||||
// Load data
|
||||
Vector<T> vData = Unsafe.ReadUnaligned<Vector<T>>(
|
||||
ref Unsafe.As<T, byte>(ref Unsafe.Add(ref start, i))
|
||||
);
|
||||
|
||||
// Compare: GreaterThanOrEqual is not directly supported by Vector<T> on all hardware,
|
||||
// but LessThan IS. So we invert: !(Data < Key)
|
||||
// Wait! We want First GreaterOrEqual.
|
||||
// Sorted array: [10, 20, 30, 40]. Search 25.
|
||||
// 10 < 25 (True), 20 < 25 (True), 30 < 25 (False), 40 < 25 (False).
|
||||
// The first "False" is our target.
|
||||
|
||||
Vector<T> vLessThan = Vector.LessThan(vData, vKey);
|
||||
|
||||
// If NOT all are less than key (i.e., some are >= key), we found the block.
|
||||
if (vLessThan != Vector<T>.One) // Vector.One is all bits set (True)
|
||||
{
|
||||
// Iterate this small block to find the exact index
|
||||
// (There are fancier bit-twiddling ways, but a tight loop over 4-8 items is instant)
|
||||
for (int j = 0; j < vectorSize; j++)
|
||||
{
|
||||
// Re-check locally
|
||||
if (Comparer<T>.Default.Compare(Unsafe.Add(ref start, i + j), key) >= 0)
|
||||
{
|
||||
return i + j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i += vectorSize;
|
||||
}
|
||||
|
||||
// 3. Scalar Cleanup (Tail)
|
||||
while (i < len)
|
||||
{
|
||||
if (Comparer<T>.Default.Compare(Unsafe.Add(ref start, i), key) >= 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int SearchNumericRoutingSIMD<T>(Span<T> keys, T key)
|
||||
where T : struct, INumber<T>
|
||||
{
|
||||
if (!Vector<T>.IsSupported) return LinearSearchRouting(keys, key);
|
||||
|
||||
int len = keys.Length;
|
||||
int vectorSize = Vector<T>.Count;
|
||||
int i = 0;
|
||||
Vector<T> vKey = new Vector<T>(key);
|
||||
|
||||
ref T start = ref MemoryMarshal.GetReference(keys);
|
||||
|
||||
while (i <= len - vectorSize)
|
||||
{
|
||||
Vector<T> vData = Unsafe.ReadUnaligned<Vector<T>>(
|
||||
ref Unsafe.As<T, byte>(ref Unsafe.Add(ref start, i))
|
||||
);
|
||||
|
||||
// ROUTING LOGIC: We want STRICTLY GREATER (>).
|
||||
// Vector.GreaterThan returns -1 (All 1s) for True, 0 for False.
|
||||
Vector<T> vGreater = Vector.GreaterThan(vData, vKey);
|
||||
|
||||
if (vGreater != Vector<T>.Zero)
|
||||
{
|
||||
// Found a block with values > key. Find the exact one.
|
||||
for (int j = 0; j < vectorSize; j++)
|
||||
{
|
||||
if (Comparer<T>.Default.Compare(Unsafe.Add(ref start, i + j), key) > 0)
|
||||
{
|
||||
return i + j;
|
||||
}
|
||||
}
|
||||
}
|
||||
i += vectorSize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Tail cleanup
|
||||
while (i < len)
|
||||
{
|
||||
if (Comparer<T>.Default.Compare(Unsafe.Add(ref start, i), key) > 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearSearchRouting<K, TStrategy>(Span<K> keys, K key, TStrategy strategy)
|
||||
where TStrategy : IKeyStrategy<K>
|
||||
|
|
@ -522,7 +365,10 @@ where TStrategy : IKeyStrategy<K>
|
|||
Array.Copy(leaf.Values, index, leaf.Values, index + 1, count - index);
|
||||
|
||||
if (strategy.UsesPrefixes)
|
||||
Array.Copy(leaf._prefixes!, index, leaf._prefixes!, index + 1, count - index);
|
||||
{
|
||||
leaf.AllPrefixes.Slice(index, count-index)
|
||||
.CopyTo(leaf.AllPrefixes.Slice(index+1));
|
||||
}
|
||||
}
|
||||
|
||||
leaf.Keys[index] = key;
|
||||
|
|
@ -530,7 +376,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
// This fails if leaf.Values is a Span<V> of length 'count'
|
||||
leaf.Values[index] = value;
|
||||
if (strategy.UsesPrefixes)
|
||||
leaf._prefixes![index] = strategy.GetPrefix(key);
|
||||
leaf.AllPrefixes![index] = strategy.GetPrefix(key);
|
||||
|
||||
leaf.SetCount(count + 1);
|
||||
}
|
||||
|
|
@ -554,7 +400,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
Array.Copy(left.Values, splitPoint, right.Values, 0, moveCount);
|
||||
// Manually copy prefixes if needed or re-calculate
|
||||
if (strategy.UsesPrefixes)
|
||||
for(int i=0; i<moveCount; i++) right._prefixes[i] = left._prefixes[splitPoint+i];
|
||||
for(int i=0; i<moveCount; i++) right.AllPrefixes[i] = left.AllPrefixes[splitPoint+i];
|
||||
}
|
||||
|
||||
// Update Counts
|
||||
|
|
@ -591,7 +437,10 @@ where TStrategy : IKeyStrategy<K>
|
|||
|
||||
// FIX: Shift raw prefix array
|
||||
if (strategy.UsesPrefixes)
|
||||
Array.Copy(node._prefixes!, index, node._prefixes!, index + 1, count - index);
|
||||
{
|
||||
node.AllPrefixes.Slice(index, count-index)
|
||||
.CopyTo(node.AllPrefixes.Slice(index +1));
|
||||
}
|
||||
}
|
||||
|
||||
// Shift Children
|
||||
|
|
@ -607,7 +456,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
|
||||
// FIX: Write to raw array
|
||||
if (strategy.UsesPrefixes)
|
||||
node._prefixes![index] = strategy.GetPrefix(separator);
|
||||
node.AllPrefixes![index] = strategy.GetPrefix(separator);
|
||||
|
||||
node.Children[index + 1] = newChild;
|
||||
node.SetCount(count + 1);
|
||||
|
|
@ -629,7 +478,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
int moveCount = count - splitPoint - 1; // -1 because splitPoint key goes up
|
||||
Array.Copy(left.Keys, splitPoint + 1, right.Keys, 0, moveCount);
|
||||
if (strategy.UsesPrefixes)
|
||||
for(int i=0; i<moveCount; i++) right._prefixes[i] = left._prefixes[splitPoint + 1 + i];
|
||||
for(int i=0; i<moveCount; i++) right.AllPrefixes[i] = left.AllPrefixes[splitPoint + 1 + i];
|
||||
|
||||
// Move Children to Right
|
||||
// Left has children 0..splitPoint. Right has children splitPoint+1..End
|
||||
|
|
@ -685,7 +534,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
var p = leaf._prefixes;
|
||||
var p = leaf.AllPrefixes;
|
||||
for (int i = index; i < count - 1; i++) p[i] = p[i + 1];
|
||||
}
|
||||
|
||||
|
|
@ -759,7 +608,10 @@ where TStrategy : IKeyStrategy<K>
|
|||
Array.Copy(rightLeaf.Keys, 0, leftLeaf.Keys, lCount, rCount);
|
||||
Array.Copy(rightLeaf.Values, 0, leftLeaf.Values, lCount, rCount);
|
||||
if (strategy.UsesPrefixes)
|
||||
Array.Copy(rightLeaf._prefixes, 0, leftLeaf._prefixes, lCount, rCount);
|
||||
{
|
||||
rightLeaf.AllPrefixes.Slice(0, rCount)
|
||||
.CopyTo(leftLeaf.AllPrefixes.Slice(lCount));
|
||||
}
|
||||
|
||||
leftLeaf.SetCount(lCount + rCount);
|
||||
leftLeaf.Next = rightLeaf.Next;
|
||||
|
|
@ -776,12 +628,15 @@ where TStrategy : IKeyStrategy<K>
|
|||
int lCount = leftInternal.Header.Count;
|
||||
leftInternal.Keys[lCount] = separator;
|
||||
if (strategy.UsesPrefixes)
|
||||
leftInternal._prefixes[lCount] = strategy.GetPrefix(separator);
|
||||
leftInternal.AllPrefixes[lCount] = strategy.GetPrefix(separator);
|
||||
|
||||
int rCount = rightInternal.Header.Count;
|
||||
Array.Copy(rightInternal.Keys, 0, leftInternal.Keys, lCount + 1, rCount);
|
||||
if (strategy.UsesPrefixes)
|
||||
Array.Copy(rightInternal._prefixes, 0, leftInternal._prefixes, lCount + 1, rCount);
|
||||
{
|
||||
rightInternal.AllPrefixes.Slice(0, rCount)
|
||||
.CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1));
|
||||
}
|
||||
|
||||
for (int i = 0; i <= rCount; i++)
|
||||
{
|
||||
|
|
@ -796,7 +651,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
Array.Copy(parent.Keys, separatorIndex + 1, parent.Keys, separatorIndex, pCount - separatorIndex - 1);
|
||||
if (strategy.UsesPrefixes)
|
||||
{
|
||||
var pp = parent._prefixes;
|
||||
var pp = parent.AllPrefixes;
|
||||
for (int i = separatorIndex; i < pCount - 1; i++) pp[i] = pp[i + 1];
|
||||
}
|
||||
|
||||
|
|
@ -824,7 +679,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
// Update Parent Separator
|
||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
||||
if (strategy.UsesPrefixes)
|
||||
parent._prefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -838,7 +693,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
// 2. Move Right[0] Key to Parent
|
||||
parent.Keys[separatorIndex] = rightInternal.Keys[0];
|
||||
if (strategy.UsesPrefixes)
|
||||
parent._prefixes[separatorIndex] = strategy.GetPrefix(rightInternal.Keys[0]);
|
||||
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.
|
||||
|
|
@ -851,7 +706,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
|
||||
// Shift keys
|
||||
Array.Copy(rightInternal.Keys, 1, rightInternal.Keys, 0, rCount - 1);
|
||||
var rp = rightInternal._prefixes;
|
||||
var rp = rightInternal.AllPrefixes;
|
||||
for(int i=0; i<rCount-1; i++) rp[i] = rp[i+1];
|
||||
|
||||
rightInternal.SetCount(rCount - 1);
|
||||
|
|
@ -873,7 +728,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
|
||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
||||
if (strategy.UsesPrefixes)
|
||||
parent._prefixes![separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||
parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -889,7 +744,7 @@ where TStrategy : IKeyStrategy<K>
|
|||
// 2. Move Left[last] Key to Parent
|
||||
parent.Keys[separatorIndex] = leftInternal.Keys[last];
|
||||
if (strategy.UsesPrefixes)
|
||||
parent._prefixes![separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]);
|
||||
parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]);
|
||||
|
||||
// 3. Truncate Left
|
||||
leftInternal.SetCount(last);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ public interface IKeyStrategy<K>
|
|||
long GetPrefix(K key);
|
||||
|
||||
bool UsesPrefixes => true;
|
||||
|
||||
//
|
||||
bool IsLossless => false;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -58,17 +61,20 @@ public struct IntStrategy : IKeyStrategy<int>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(int x, int y) => x.CompareTo(y);
|
||||
|
||||
public bool UsesPrefixes => false;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(int key)
|
||||
{
|
||||
return 0;
|
||||
// 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 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);
|
||||
|
|
|
|||
|
|
@ -50,9 +50,6 @@ public abstract class Node<K>
|
|||
{
|
||||
public NodeHeader Header;
|
||||
|
||||
// FIX: Change to 'internal' so BTreeFunctions can shift the array directly.
|
||||
internal long[]? _prefixes;
|
||||
|
||||
protected Node(OwnerId owner, NodeFlags flags)
|
||||
{
|
||||
Header = new NodeHeader(owner, 0, flags);
|
||||
|
|
@ -60,8 +57,11 @@ public abstract class Node<K>
|
|||
|
||||
public abstract Span<K> GetKeys();
|
||||
|
||||
// Keep this for Search (Read-Only): it limits the view to valid items only.
|
||||
public Span<long> Prefixes => _prefixes.AsSpan(0, Header.Count);
|
||||
// Abstract access to prefixes regardless of storage backing
|
||||
public abstract Span<long> AllPrefixes { get; }
|
||||
|
||||
public Span<long> Prefixes => AllPrefixes.Slice(0, Header.Count);
|
||||
|
||||
|
||||
public bool IsLeaf => (Header.Flags & NodeFlags.IsLeaf) != 0;
|
||||
|
||||
|
|
@ -89,48 +89,31 @@ public sealed class LeafNode<K, V> : Node<K>
|
|||
public const int Capacity = 64;
|
||||
public const int MergeThreshold = 8;
|
||||
|
||||
// Leaf stores Keys and Values
|
||||
public K[] Keys;
|
||||
|
||||
public LeafNode<K, V>? Next; // For range scans
|
||||
public K[]? Keys;
|
||||
public V[] Values;
|
||||
public LeafNode<K, V>? Next;
|
||||
|
||||
internal long[]? _prefixes;
|
||||
|
||||
public override Span<long> AllPrefixes => _prefixes != null ? _prefixes : Span<long>.Empty;
|
||||
|
||||
public LeafNode(OwnerId owner) : base(owner, NodeFlags.IsLeaf | NodeFlags.HasPrefixes)
|
||||
{
|
||||
Keys = new K[Capacity];
|
||||
Values = new V[Capacity];
|
||||
if (typeof(K) == typeof(int)
|
||||
|| typeof(K) == typeof(long))
|
||||
{
|
||||
_prefixes = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_prefixes = new long[Capacity];
|
||||
}
|
||||
_prefixes = new long[Capacity];
|
||||
|
||||
}
|
||||
|
||||
// Copy Constructor for CoW
|
||||
private LeafNode(LeafNode<K, V> original, OwnerId newOwner)
|
||||
: base(newOwner, original.Header.Flags)
|
||||
{
|
||||
Header.Count = original.Header.Count;
|
||||
Next = original.Next;
|
||||
|
||||
// Allocate new arrays
|
||||
Keys = new K[Capacity];
|
||||
Values = new V[Capacity];
|
||||
|
||||
if (typeof(K) == typeof(int)
|
||||
|| typeof(K) == typeof(long))
|
||||
{
|
||||
_prefixes = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_prefixes = new long[Capacity];
|
||||
}
|
||||
Header.Count = original.Header.Count;
|
||||
Next = original.Next;
|
||||
_prefixes = new long[Capacity];
|
||||
|
||||
// Copy data
|
||||
Array.Copy(original.Keys, Keys, original.Header.Count);
|
||||
|
|
@ -177,25 +160,17 @@ public sealed class InternalNode<K> : Node<K>
|
|||
{
|
||||
public const int Capacity = 32;
|
||||
|
||||
// Inline buffer for children (no array object overhead)
|
||||
// InlineArray storage
|
||||
internal InternalPrefixBuffer _prefixBuffer;
|
||||
public NodeBuffer<K> Children;
|
||||
|
||||
// Internal stores Keys (separators) and Children
|
||||
public K[] Keys;
|
||||
public K[]? Keys;
|
||||
|
||||
public override Span<long> AllPrefixes => MemoryMarshal.CreateSpan(ref _prefixBuffer[0], Capacity);
|
||||
|
||||
public InternalNode(OwnerId owner) : base(owner, NodeFlags.HasPrefixes)
|
||||
{
|
||||
Keys = new K[Capacity];
|
||||
if (typeof(K) == typeof(int)
|
||||
|| typeof(K) == typeof(long))
|
||||
{
|
||||
_prefixes = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_prefixes = new long[Capacity];
|
||||
}
|
||||
// Children buffer is a struct, zero-initialized by default
|
||||
}
|
||||
|
||||
|
|
@ -204,27 +179,12 @@ public sealed class InternalNode<K> : Node<K>
|
|||
: base(newOwner, original.Header.Flags)
|
||||
{
|
||||
Header.Count = original.Header.Count;
|
||||
|
||||
Keys = new K[Capacity];
|
||||
if (typeof(K) == typeof(int)
|
||||
|| typeof(K) == typeof(long))
|
||||
{
|
||||
|
||||
_prefixes = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_prefixes = new long[Capacity];
|
||||
}
|
||||
|
||||
// Copy Keys and Prefixes
|
||||
Array.Copy(original.Keys, Keys, original.Header.Count);
|
||||
if (original._prefixes != null)
|
||||
Array.Copy(original._prefixes, _prefixes, original.Header.Count);
|
||||
|
||||
// Copy Children (Manual loop required for InlineArray in generic context usually,
|
||||
// but here we can iterate the span)
|
||||
// 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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ using PersistentMap;
|
|||
[MemoryDiagnoser]
|
||||
public class ImmutableBenchmark
|
||||
{
|
||||
[Params(10, 100, 1000)]
|
||||
[Params(10, 100)]
|
||||
public int N { get; set; }
|
||||
|
||||
[Params(1000)]
|
||||
[Params(10000)]
|
||||
public int CollectionSize { get; set; }
|
||||
|
||||
private ImmutableDictionary<string, string> _immutableDict;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue