Rename because it is ordered
This commit is contained in:
parent
b5b363ae9f
commit
e3cec3423b
28 changed files with 104 additions and 104 deletions
17
PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs
Normal file
17
PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace PersistentOrderedMap;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// This is a comparable strategy that may squeeze some extra time out of value types
|
||||
|
||||
public readonly struct ComparableStrategy<K> : IKeyStrategy<K> where K : IComparable<K>
|
||||
{
|
||||
public bool UsesPrefixes => false;
|
||||
public bool UseBinarySearch => true;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetPrefix(K key) => 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(K x, K y) => x.CompareTo(y);
|
||||
}
|
||||
34
PersistentOrderedMap/KeyStrategies/DoubleStrategy.cs
Normal file
34
PersistentOrderedMap/KeyStrategies/DoubleStrategy.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
namespace PersistentOrderedMap;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
168
PersistentOrderedMap/KeyStrategies/IntScanner.cs
Normal file
168
PersistentOrderedMap/KeyStrategies/IntScanner.cs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace PersistentOrderedMap;
|
||||
|
||||
public static class IntScanner
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int FindFirstGreaterOrEqual(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
// Fallback for short arrays or unsupported hardware.
|
||||
// AVX2 processes 8 integers at a time.
|
||||
if (!Avx2.IsSupported || keys.Length < 8)
|
||||
return LinearScan(keys, target);
|
||||
|
||||
return Avx512F.IsSupported
|
||||
? ScanAvx512(keys, target)
|
||||
: ScanAvx2(keys, target);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearScan(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
if (keys[i] >= target)
|
||||
return i;
|
||||
return keys.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx2(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
// AVX2 lacks a native GreaterOrEqual for 32-bit integers.
|
||||
// We use GreaterThan(Data, target - 1).
|
||||
var vTarget = Vector256.Create(target - 1);
|
||||
var i = 0;
|
||||
var len = keys.Length;
|
||||
|
||||
for (; i <= len - 8; i += 8)
|
||||
{
|
||||
fixed (int* ptr = keys)
|
||||
{
|
||||
var vData = Avx2.LoadVector256(ptr + i);
|
||||
var vResult = Avx2.CompareGreaterThan(vData, vTarget);
|
||||
|
||||
// MoveMask creates a 32-bit integer from the most significant bit of each byte.
|
||||
var mask = (uint)Avx2.MoveMask(vResult.AsByte());
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
// Since an int is 4 bytes, MoveMask sets 4 bits per matching element.
|
||||
// Dividing the trailing zero count by 4 maps the byte offset back to the integer index.
|
||||
return i + (BitOperations.TrailingZeroCount(mask) / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScan(keys.Slice(i), target) + i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx512(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
// AVX-512 processes 16 integers (512 bits) per instruction.
|
||||
var vTarget = Vector512.Create(target);
|
||||
var i = 0;
|
||||
var len = keys.Length;
|
||||
|
||||
for (; i <= len - 16; i += 16)
|
||||
{
|
||||
fixed (int* ptr = keys)
|
||||
{
|
||||
var vData = Avx512F.LoadVector512(ptr + i);
|
||||
|
||||
// Vector512 API is used directly here to cleanly get the mask
|
||||
var mask = Vector512.GreaterThanOrEqual(vData, vTarget);
|
||||
|
||||
if (mask != Vector512<int>.Zero)
|
||||
{
|
||||
uint m = (uint)mask.ExtractMostSignificantBits();
|
||||
return i + BitOperations.TrailingZeroCount(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScan(keys.Slice(i), target) + i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int FindFirstGreater(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
if (!Avx2.IsSupported || keys.Length < 8)
|
||||
return LinearScanGreater(keys, target);
|
||||
|
||||
return Avx512F.IsSupported
|
||||
? ScanAvx512Greater(keys, target)
|
||||
: ScanAvx2Greater(keys, target);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int LinearScanGreater(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
if (keys[i] > target)
|
||||
return i;
|
||||
return keys.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx2Greater(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
// For > target, AVX2 CompareGreaterThan works directly without the (target - 1) offset
|
||||
var vTarget = Vector256.Create(target);
|
||||
var i = 0;
|
||||
var len = keys.Length;
|
||||
|
||||
for (; i <= len - 8; i += 8)
|
||||
{
|
||||
fixed (int* ptr = keys)
|
||||
{
|
||||
var vData = Avx2.LoadVector256(ptr + i);
|
||||
var vResult = Avx2.CompareGreaterThan(vData, vTarget);
|
||||
|
||||
var mask = (uint)Avx2.MoveMask(vResult.AsByte());
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
return i + (BitOperations.TrailingZeroCount(mask) / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScanGreater(keys.Slice(i), target) + i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe int ScanAvx512Greater(ReadOnlySpan<int> keys, int target)
|
||||
{
|
||||
var vTarget = Vector512.Create(target);
|
||||
var i = 0;
|
||||
var len = keys.Length;
|
||||
|
||||
for (; i <= len - 16; i += 16)
|
||||
{
|
||||
fixed (int* ptr = keys)
|
||||
{
|
||||
var vData = Avx512F.LoadVector512(ptr + i);
|
||||
|
||||
// Use GreaterThan instead of GreaterThanOrEqual
|
||||
var mask = Vector512.GreaterThan(vData, vTarget);
|
||||
|
||||
if (mask != Vector512<int>.Zero)
|
||||
{
|
||||
uint m = (uint)mask.ExtractMostSignificantBits();
|
||||
return i + BitOperations.TrailingZeroCount(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LinearScanGreater(keys.Slice(i), target) + i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
16
PersistentOrderedMap/KeyStrategies/IntStrategy.cs
Normal file
16
PersistentOrderedMap/KeyStrategies/IntStrategy.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
namespace PersistentOrderedMap;
|
||||
|
||||
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
|
||||
}
|
||||
101
PersistentOrderedMap/KeyStrategies/PrefixScanner.cs
Normal file
101
PersistentOrderedMap/KeyStrategies/PrefixScanner.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
namespace PersistentOrderedMap;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
53
PersistentOrderedMap/KeyStrategies/StandardStrategy.cs
Normal file
53
PersistentOrderedMap/KeyStrategies/StandardStrategy.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
namespace PersistentOrderedMap;
|
||||
|
||||
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;
|
||||
public bool UseBinarySearch => true;
|
||||
// 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 readonly struct StandardStrategy2<K, TComparer> : IKeyStrategy<K>
|
||||
where TComparer : struct, IComparer<K>
|
||||
{
|
||||
private readonly TComparer _comparer;
|
||||
|
||||
public StandardStrategy2(TComparer comparer) => _comparer = comparer;
|
||||
|
||||
public bool UsesPrefixes => false;
|
||||
public bool UseBinarySearch => true;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(K x, K y) => _comparer.Compare(x, y);
|
||||
|
||||
public long GetPrefix(K key) => 0;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue