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.Children[1] = splitResult.NewNode;
|
||||||
newRoot.SetCount(1);
|
newRoot.SetCount(1);
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
newRoot._prefixes![0] = strategy.GetPrefix(splitResult.Separator);
|
newRoot.AllPrefixes[0] = strategy.GetPrefix(splitResult.Separator);
|
||||||
|
|
||||||
return newRoot;
|
return newRoot;
|
||||||
}
|
}
|
||||||
|
|
@ -194,32 +194,6 @@ where TStrategy : IKeyStrategy<K>
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<K> keys = node.GetKeys();
|
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);
|
return LinearSearchKeys(node.GetKeys(), key, strategy);
|
||||||
|
|
@ -259,25 +233,6 @@ where TStrategy : IKeyStrategy<K>
|
||||||
{
|
{
|
||||||
if (!strategy.UsesPrefixes)
|
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
|
// C. Fallback
|
||||||
return LinearSearchRouting(node.GetKeys(), key, strategy);
|
return LinearSearchRouting(node.GetKeys(), key, strategy);
|
||||||
}
|
}
|
||||||
|
|
@ -292,118 +247,6 @@ where TStrategy : IKeyStrategy<K>
|
||||||
return RefineRouting(index, node.Keys, node.Header.Count, key, strategy);
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int LinearSearchRouting<K, TStrategy>(Span<K> keys, K key, TStrategy strategy)
|
private static int LinearSearchRouting<K, TStrategy>(Span<K> keys, K key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<K>
|
||||||
|
|
@ -520,9 +363,12 @@ where TStrategy : IKeyStrategy<K>
|
||||||
|
|
||||||
// This fails if leaf.Values is a Span<V> of length 'count'
|
// This fails if leaf.Values is a Span<V> of length 'count'
|
||||||
Array.Copy(leaf.Values, index, leaf.Values, index + 1, count - index);
|
Array.Copy(leaf.Values, index, leaf.Values, index + 1, count - index);
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
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;
|
leaf.Keys[index] = key;
|
||||||
|
|
@ -530,7 +376,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
// This fails if leaf.Values is a Span<V> of length 'count'
|
// This fails if leaf.Values is a Span<V> of length 'count'
|
||||||
leaf.Values[index] = value;
|
leaf.Values[index] = value;
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
leaf._prefixes![index] = strategy.GetPrefix(key);
|
leaf.AllPrefixes![index] = strategy.GetPrefix(key);
|
||||||
|
|
||||||
leaf.SetCount(count + 1);
|
leaf.SetCount(count + 1);
|
||||||
}
|
}
|
||||||
|
|
@ -554,7 +400,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
Array.Copy(left.Values, splitPoint, right.Values, 0, moveCount);
|
Array.Copy(left.Values, splitPoint, right.Values, 0, moveCount);
|
||||||
// Manually copy prefixes if needed or re-calculate
|
// Manually copy prefixes if needed or re-calculate
|
||||||
if (strategy.UsesPrefixes)
|
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
|
// Update Counts
|
||||||
|
|
@ -591,7 +437,10 @@ where TStrategy : IKeyStrategy<K>
|
||||||
|
|
||||||
// FIX: Shift raw prefix array
|
// FIX: Shift raw prefix array
|
||||||
if (strategy.UsesPrefixes)
|
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
|
// Shift Children
|
||||||
|
|
@ -607,7 +456,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
|
|
||||||
// FIX: Write to raw array
|
// FIX: Write to raw array
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
node._prefixes![index] = strategy.GetPrefix(separator);
|
node.AllPrefixes![index] = strategy.GetPrefix(separator);
|
||||||
|
|
||||||
node.Children[index + 1] = newChild;
|
node.Children[index + 1] = newChild;
|
||||||
node.SetCount(count + 1);
|
node.SetCount(count + 1);
|
||||||
|
|
@ -629,7 +478,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
int moveCount = count - splitPoint - 1; // -1 because splitPoint key goes up
|
int moveCount = count - splitPoint - 1; // -1 because splitPoint key goes up
|
||||||
Array.Copy(left.Keys, splitPoint + 1, right.Keys, 0, moveCount);
|
Array.Copy(left.Keys, splitPoint + 1, right.Keys, 0, moveCount);
|
||||||
if (strategy.UsesPrefixes)
|
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
|
// Move Children to Right
|
||||||
// Left has children 0..splitPoint. Right has children splitPoint+1..End
|
// Left has children 0..splitPoint. Right has children splitPoint+1..End
|
||||||
|
|
@ -685,7 +534,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
{
|
{
|
||||||
var p = leaf._prefixes;
|
var p = leaf.AllPrefixes;
|
||||||
for (int i = index; i < count - 1; i++) p[i] = p[i + 1];
|
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.Keys, 0, leftLeaf.Keys, lCount, rCount);
|
||||||
Array.Copy(rightLeaf.Values, 0, leftLeaf.Values, lCount, rCount);
|
Array.Copy(rightLeaf.Values, 0, leftLeaf.Values, lCount, rCount);
|
||||||
if (strategy.UsesPrefixes)
|
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.SetCount(lCount + rCount);
|
||||||
leftLeaf.Next = rightLeaf.Next;
|
leftLeaf.Next = rightLeaf.Next;
|
||||||
|
|
@ -776,12 +628,15 @@ where TStrategy : IKeyStrategy<K>
|
||||||
int lCount = leftInternal.Header.Count;
|
int lCount = leftInternal.Header.Count;
|
||||||
leftInternal.Keys[lCount] = separator;
|
leftInternal.Keys[lCount] = separator;
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
leftInternal._prefixes[lCount] = strategy.GetPrefix(separator);
|
leftInternal.AllPrefixes[lCount] = strategy.GetPrefix(separator);
|
||||||
|
|
||||||
int rCount = rightInternal.Header.Count;
|
int rCount = rightInternal.Header.Count;
|
||||||
Array.Copy(rightInternal.Keys, 0, leftInternal.Keys, lCount + 1, rCount);
|
Array.Copy(rightInternal.Keys, 0, leftInternal.Keys, lCount + 1, rCount);
|
||||||
if (strategy.UsesPrefixes)
|
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++)
|
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);
|
Array.Copy(parent.Keys, separatorIndex + 1, parent.Keys, separatorIndex, pCount - separatorIndex - 1);
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
{
|
{
|
||||||
var pp = parent._prefixes;
|
var pp = parent.AllPrefixes;
|
||||||
for (int i = separatorIndex; i < pCount - 1; i++) pp[i] = pp[i + 1];
|
for (int i = separatorIndex; i < pCount - 1; i++) pp[i] = pp[i + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -824,7 +679,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
// Update Parent Separator
|
// Update Parent Separator
|
||||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
parent._prefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -838,7 +693,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
// 2. Move Right[0] Key to Parent
|
// 2. Move Right[0] Key to Parent
|
||||||
parent.Keys[separatorIndex] = rightInternal.Keys[0];
|
parent.Keys[separatorIndex] = rightInternal.Keys[0];
|
||||||
if (strategy.UsesPrefixes)
|
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)
|
// 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.
|
// 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
|
// Shift keys
|
||||||
Array.Copy(rightInternal.Keys, 1, rightInternal.Keys, 0, rCount - 1);
|
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];
|
for(int i=0; i<rCount-1; i++) rp[i] = rp[i+1];
|
||||||
|
|
||||||
rightInternal.SetCount(rCount - 1);
|
rightInternal.SetCount(rCount - 1);
|
||||||
|
|
@ -873,7 +728,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
|
|
||||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
parent._prefixes![separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -889,7 +744,7 @@ where TStrategy : IKeyStrategy<K>
|
||||||
// 2. Move Left[last] Key to Parent
|
// 2. Move Left[last] Key to Parent
|
||||||
parent.Keys[separatorIndex] = leftInternal.Keys[last];
|
parent.Keys[separatorIndex] = leftInternal.Keys[last];
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
parent._prefixes![separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]);
|
parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]);
|
||||||
|
|
||||||
// 3. Truncate Left
|
// 3. Truncate Left
|
||||||
leftInternal.SetCount(last);
|
leftInternal.SetCount(last);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ public interface IKeyStrategy<K>
|
||||||
long GetPrefix(K key);
|
long GetPrefix(K key);
|
||||||
|
|
||||||
bool UsesPrefixes => true;
|
bool UsesPrefixes => true;
|
||||||
|
|
||||||
|
//
|
||||||
|
bool IsLossless => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,17 +61,20 @@ public struct IntStrategy : IKeyStrategy<int>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int Compare(int x, int y) => x.CompareTo(y);
|
public int Compare(int x, int y) => x.CompareTo(y);
|
||||||
|
|
||||||
public bool UsesPrefixes => false;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public long GetPrefix(int key)
|
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 struct DoubleStrategy : IKeyStrategy<double>
|
||||||
{
|
{
|
||||||
|
public bool IsLossless => true;
|
||||||
// Use the standard comparison for the fallback/refine step
|
// Use the standard comparison for the fallback/refine step
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int Compare(double x, double y) => x.CompareTo(y);
|
public int Compare(double x, double y) => x.CompareTo(y);
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,6 @@ internal struct InternalPrefixBuffer
|
||||||
public abstract class Node<K>
|
public abstract class Node<K>
|
||||||
{
|
{
|
||||||
public NodeHeader Header;
|
public NodeHeader Header;
|
||||||
|
|
||||||
// FIX: Change to 'internal' so BTreeFunctions can shift the array directly.
|
|
||||||
internal long[]? _prefixes;
|
|
||||||
|
|
||||||
protected Node(OwnerId owner, NodeFlags flags)
|
protected Node(OwnerId owner, NodeFlags flags)
|
||||||
{
|
{
|
||||||
|
|
@ -59,9 +56,12 @@ public abstract class Node<K>
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Span<K> GetKeys();
|
public abstract Span<K> GetKeys();
|
||||||
|
|
||||||
|
// Abstract access to prefixes regardless of storage backing
|
||||||
|
public abstract Span<long> AllPrefixes { get; }
|
||||||
|
|
||||||
|
public Span<long> Prefixes => AllPrefixes.Slice(0, Header.Count);
|
||||||
|
|
||||||
// Keep this for Search (Read-Only): it limits the view to valid items only.
|
|
||||||
public Span<long> Prefixes => _prefixes.AsSpan(0, Header.Count);
|
|
||||||
|
|
||||||
public bool IsLeaf => (Header.Flags & NodeFlags.IsLeaf) != 0;
|
public bool IsLeaf => (Header.Flags & NodeFlags.IsLeaf) != 0;
|
||||||
|
|
||||||
|
|
@ -88,49 +88,32 @@ public sealed class LeafNode<K, V> : Node<K>
|
||||||
{
|
{
|
||||||
public const int Capacity = 64;
|
public const int Capacity = 64;
|
||||||
public const int MergeThreshold = 8;
|
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 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)
|
public LeafNode(OwnerId owner) : base(owner, NodeFlags.IsLeaf | NodeFlags.HasPrefixes)
|
||||||
{
|
{
|
||||||
Keys = new K[Capacity];
|
Keys = new K[Capacity];
|
||||||
Values = new V[Capacity];
|
Values = new V[Capacity];
|
||||||
if (typeof(K) == typeof(int)
|
_prefixes = new long[Capacity];
|
||||||
|| typeof(K) == typeof(long))
|
|
||||||
{
|
|
||||||
_prefixes = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_prefixes = new long[Capacity];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy Constructor for CoW
|
// Copy Constructor for CoW
|
||||||
private LeafNode(LeafNode<K, V> original, OwnerId newOwner)
|
private LeafNode(LeafNode<K, V> original, OwnerId newOwner)
|
||||||
: base(newOwner, original.Header.Flags)
|
: base(newOwner, original.Header.Flags)
|
||||||
{
|
{
|
||||||
Header.Count = original.Header.Count;
|
|
||||||
Next = original.Next;
|
|
||||||
|
|
||||||
// Allocate new arrays
|
|
||||||
Keys = new K[Capacity];
|
Keys = new K[Capacity];
|
||||||
Values = new V[Capacity];
|
Values = new V[Capacity];
|
||||||
|
Header.Count = original.Header.Count;
|
||||||
if (typeof(K) == typeof(int)
|
Next = original.Next;
|
||||||
|| typeof(K) == typeof(long))
|
_prefixes = new long[Capacity];
|
||||||
{
|
|
||||||
_prefixes = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
_prefixes = new long[Capacity];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy data
|
// Copy data
|
||||||
Array.Copy(original.Keys, Keys, original.Header.Count);
|
Array.Copy(original.Keys, Keys, original.Header.Count);
|
||||||
|
|
@ -177,25 +160,17 @@ public sealed class InternalNode<K> : Node<K>
|
||||||
{
|
{
|
||||||
public const int Capacity = 32;
|
public const int Capacity = 32;
|
||||||
|
|
||||||
// Inline buffer for children (no array object overhead)
|
// InlineArray storage
|
||||||
|
internal InternalPrefixBuffer _prefixBuffer;
|
||||||
public NodeBuffer<K> Children;
|
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)
|
public InternalNode(OwnerId owner) : base(owner, NodeFlags.HasPrefixes)
|
||||||
{
|
{
|
||||||
Keys = new K[Capacity];
|
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
|
// 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)
|
: base(newOwner, original.Header.Flags)
|
||||||
{
|
{
|
||||||
Header.Count = original.Header.Count;
|
Header.Count = original.Header.Count;
|
||||||
|
|
||||||
Keys = new K[Capacity];
|
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);
|
Array.Copy(original.Keys, Keys, original.Header.Count);
|
||||||
if (original._prefixes != null)
|
|
||||||
Array.Copy(original._prefixes, _prefixes, original.Header.Count);
|
// Fast struct blit for prefixes
|
||||||
|
this._prefixBuffer = original._prefixBuffer;
|
||||||
|
|
||||||
// Copy Children (Manual loop required for InlineArray in generic context usually,
|
|
||||||
// but here we can iterate the span)
|
|
||||||
var srcChildren = original.GetChildren();
|
var srcChildren = original.GetChildren();
|
||||||
for (var i = 0; i < srcChildren.Length; i++) Children[i] = srcChildren[i];
|
for (var i = 0; i < srcChildren.Length; i++) Children[i] = srcChildren[i];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ using PersistentMap;
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class ImmutableBenchmark
|
public class ImmutableBenchmark
|
||||||
{
|
{
|
||||||
[Params(10, 100, 1000)]
|
[Params(10, 100)]
|
||||||
public int N { get; set; }
|
public int N { get; set; }
|
||||||
|
|
||||||
[Params(1000)]
|
[Params(10000)]
|
||||||
public int CollectionSize { get; set; }
|
public int CollectionSize { get; set; }
|
||||||
|
|
||||||
private ImmutableDictionary<string, string> _immutableDict;
|
private ImmutableDictionary<string, string> _immutableDict;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue