Add a standard key strategy for maps without prefixe
Remove the stupid "next" field
This commit is contained in:
parent
7bea233edc
commit
4d87e30b40
3 changed files with 48 additions and 18 deletions
|
|
@ -13,14 +13,45 @@ public interface IKeyStrategy<K>
|
||||||
{
|
{
|
||||||
int Compare(K x, K y);
|
int Compare(K x, K y);
|
||||||
long GetPrefix(K key);
|
long GetPrefix(K key);
|
||||||
|
|
||||||
bool UsesPrefixes => true;
|
bool UsesPrefixes => true;
|
||||||
|
|
||||||
//
|
//
|
||||||
bool IsLossless => false;
|
bool IsLossless => 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>
|
public struct UnicodeStrategy : IKeyStrategy<string>
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -33,17 +64,17 @@ public struct UnicodeStrategy : IKeyStrategy<string>
|
||||||
|
|
||||||
// 1. Prepare Buffer (8 bytes)
|
// 1. Prepare Buffer (8 bytes)
|
||||||
// stackalloc is virtually free (pointer bump)
|
// stackalloc is virtually free (pointer bump)
|
||||||
Span<byte> utf8Bytes = stackalloc byte[8];
|
Span<byte> utf8Bytes = stackalloc byte[8];
|
||||||
|
|
||||||
// 2. Transcode (The "Safe" Magic)
|
// 2. Transcode (The "Safe" Magic)
|
||||||
// This intrinsic handles ASCII efficiently and converts Surrogates/Chinese
|
// This intrinsic handles ASCII efficiently and converts Surrogates/Chinese
|
||||||
// into bytes that maintain the correct "Magnitude" (Sort Order).
|
// into bytes that maintain the correct "Magnitude" (Sort Order).
|
||||||
// Invalid surrogates become 0xEF (Replacement Char), which sorts > ASCII.
|
// Invalid surrogates become 0xEF (Replacement Char), which sorts > ASCII.
|
||||||
System.Text.Unicode.Utf8.FromUtf16(
|
System.Text.Unicode.Utf8.FromUtf16(
|
||||||
key.AsSpan(0, Math.Min(key.Length, 8)),
|
key.AsSpan(0, Math.Min(key.Length, 8)),
|
||||||
utf8Bytes,
|
utf8Bytes,
|
||||||
out _,
|
out _,
|
||||||
out _,
|
out _,
|
||||||
replaceInvalidSequences: true); // True ensures we get 0xEF for broken chars
|
replaceInvalidSequences: true); // True ensures we get 0xEF for broken chars
|
||||||
|
|
||||||
// 3. Load as Big Endian Long
|
// 3. Load as Big Endian Long
|
||||||
|
|
@ -63,7 +94,7 @@ public struct IntStrategy : IKeyStrategy<int>
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public long GetPrefix(int key)
|
public long GetPrefix(int key)
|
||||||
{
|
{
|
||||||
// Pack the 32-bit int into the high 32-bits of the long.
|
// Pack the 32-bit int into the high 32-bits of the long.
|
||||||
// This preserves sorting order when scanning the long array.
|
// This preserves sorting order when scanning the long array.
|
||||||
// Cast to uint first to prevent sign extension confusion during the shift,
|
// Cast to uint first to prevent sign extension confusion during the shift,
|
||||||
|
|
@ -92,13 +123,13 @@ public struct DoubleStrategy : IKeyStrategy<double>
|
||||||
// -Negative Max -> 0
|
// -Negative Max -> 0
|
||||||
// -0 -> Midpoint
|
// -0 -> Midpoint
|
||||||
// +Negative Max -> Max
|
// +Negative Max -> Max
|
||||||
|
|
||||||
long mask = (bits >> 63); // 0 for positive, -1 (All 1s) for negative
|
long mask = (bits >> 63); // 0 for positive, -1 (All 1s) for negative
|
||||||
|
|
||||||
// If negative: bits ^ -1 = ~bits (Flip All)
|
// If negative: bits ^ -1 = ~bits (Flip All)
|
||||||
// If positive: bits ^ 0 = bits (Flip None)
|
// If positive: bits ^ 0 = bits (Flip None)
|
||||||
// Then we toggle the sign bit (0x8000...) to shift the range to signed long.
|
// Then we toggle the sign bit (0x8000...) to shift the range to signed long.
|
||||||
|
|
||||||
return (bits ^ (mask & 0x7FFFFFFFFFFFFFFF)) ^ unchecked((long)0x8000000000000000);
|
return (bits ^ (mask & 0x7FFFFFFFFFFFFFFF)) ^ unchecked((long)0x8000000000000000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +151,7 @@ public static class PrefixScanner
|
||||||
//if (targetPrefix == long.MinValue)
|
//if (targetPrefix == long.MinValue)
|
||||||
//{
|
//{
|
||||||
// return 0;
|
// return 0;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// Fallback for short arrays or unsupported hardware
|
// Fallback for short arrays or unsupported hardware
|
||||||
if (!Avx2.IsSupported || prefixes.Length < 4)
|
if (!Avx2.IsSupported || prefixes.Length < 4)
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,6 @@ public sealed class LeafNode<K, V> : Node<K>
|
||||||
|
|
||||||
public K[]? Keys;
|
public K[]? Keys;
|
||||||
public V[] Values;
|
public V[] Values;
|
||||||
public LeafNode<K, V>? Next;
|
|
||||||
|
|
||||||
internal long[]? _prefixes;
|
internal long[]? _prefixes;
|
||||||
|
|
||||||
|
|
@ -111,9 +110,7 @@ public sealed class LeafNode<K, V> : Node<K>
|
||||||
{
|
{
|
||||||
Keys = new K[Capacity];
|
Keys = new K[Capacity];
|
||||||
Values = new V[Capacity];
|
Values = new V[Capacity];
|
||||||
Header.Count = original.Header.Count;
|
Header.Count = original.Header.Count; _prefixes = new long[Capacity];
|
||||||
Next = original.Next;
|
|
||||||
_prefixes = new long[Capacity];
|
|
||||||
|
|
||||||
// Copy data
|
// Copy data
|
||||||
Array.Copy(original.Keys, Keys, original.Header.Count);
|
Array.Copy(original.Keys, Keys, original.Header.Count);
|
||||||
|
|
@ -311,4 +308,4 @@ public readonly struct OwnerId(uint id, ushort gen) : IEquatable<OwnerId>
|
||||||
{
|
{
|
||||||
return !left.Equals(right);
|
return !left.Equals(right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
* NiceBtree (PersistentMap)
|
* PersistentMap
|
||||||
|
|
||||||
A high-performance, persistent (Copy-on-Write) B+ Tree implemented in C#.
|
A high-performance, persistent (Copy-on-Write) B+ Tree implemented in C#.
|
||||||
|
|
||||||
|
|
@ -14,6 +14,8 @@ It is designed for zero-overhead reads, SIMD-accelerated key routing, and alloca
|
||||||
** When should I use this?
|
** 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 not have any problems key collisions, which will slow this map down by a lot.
|
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.
|
||||||
|
|
||||||
** Quick Start
|
** Quick Start
|
||||||
|
|
||||||
*** 1. Basic Immutable Usage
|
*** 1. Basic Immutable Usage
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue