From d97fb1dd901e14f1595224d3270b56ca4ac80316 Mon Sep 17 00:00:00 2001
From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com>
Date: Mon, 9 Mar 2026 18:14:49 +0800
Subject: [PATCH] l4 handle
---
StgSharp.Benchmark/SwissBenchmark.cs | 575 +++++++++---------
StgSharp.Common/Collections/SwissTable.X86.cs | 12 +-
StgSharp.Common/Collections/SwissTable.cs | 90 ++-
.../Memory/ConcurrentBufferStack.cs | 42 +-
.../Memory/HLSFAllocator.Alloc.cs | 23 +-
.../Memory/HLSFAllocator.Free.cs | 11 +-
.../HighPerformance/Memory/HLSFAllocator.cs | 45 +-
.../HighPerformance/Memory/L4.CommandQueue.cs | 10 +-
.../HighPerformance/Memory/L4.EvictionRing.cs | 30 +-
StgSharp.Common/HighPerformance/Memory/L4.cs | 66 +-
.../Memory/SlabAllocator.Fixed.cs | 131 ++++
.../HighPerformance/Memory/SlabAllocator.cs | 36 +-
12 files changed, 695 insertions(+), 376 deletions(-)
create mode 100644 StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs
diff --git a/StgSharp.Benchmark/SwissBenchmark.cs b/StgSharp.Benchmark/SwissBenchmark.cs
index 187c86be..47feca5e 100644
--- a/StgSharp.Benchmark/SwissBenchmark.cs
+++ b/StgSharp.Benchmark/SwissBenchmark.cs
@@ -34,82 +34,230 @@ using System.Runtime.CompilerServices;
namespace StgSharp.Benchmark
{
///
- /// Benchmark comparing SwissTable against
- /// under realistic L4 software cache address-mapping workloads.
- ///
- /// L4 scenario characteristics:
- /// - Keys are HLSF/malloc pointer addresses (large, 1024-byte aligned)
- /// - Access pattern is Zipfian-skewed (20% hot cache lines receive 80% of lookups)
- /// - Steady state: high hit-rate lookups dominate, with rare evict+refill on miss
- /// - Working set: 512–4096 active cache line mappings
- ///
+ /// Benchmark comparing SwissTable against under
+ /// realistic L4 software cache address-mapping workloads. /// L4 scenario
+ /// characteristics: /// - Keys are HLSF/malloc pointer addresses (large, 1024-byte aligned)
+ /// /// - Access pattern is Zipfian-skewed (20% hot cache lines receive 80% of lookups) ///
+ /// - Steady state: high hit-rate lookups dominate, with rare evict+refill on miss /// -
+ /// Working set: 512–4096 active cache line mappings ///
///
[MemoryDiagnoser]
public class SwissTableBench
{
- /// L4 cache line size in bytes (per L4_design.md).
+ ///
+ /// L4 cache line size in bytes (per L4_design.md).
+ ///
private const int CacheLineSize = 1024;
- /// Simulated heap base address for pointer-like keys.
+ ///
+ /// Simulated heap base address for pointer-like keys.
+ ///
private const long HeapBase = 0x7FFF_0000_0000L;
- /// Number of lookup operations per Lookup benchmark call.
+ ///
+ /// Number of lookup operations per Lookup benchmark call.
+ ///
private const int LookupIterations = 8192;
///
- /// Number of steady-state operations per call.
- /// Each op is either a lookup (hit) or an evict+insert+lookup (miss).
+ /// Fraction of steady-state ops that are cache misses.
+ ///
+ private const double MissRate = 0.05;
+
+ ///
+ /// Number of steady-state operations per call. Each op is either a lookup (hit) or an
+ /// evict+insert+lookup (miss).
///
private const int SteadyStateOps = 4096;
- /// Fraction of steady-state ops that are cache misses.
- private const double MissRate = 0.05;
+ ///
+ /// Indices into _residentKeys for eviction targets (cold entries, tail region).
+ ///
+ private int[] _evictOrder = null!;
+
+ ///
+ /// Additional keys not in the table, for triggering misses.
+ ///
+ private nuint[] _externalKeys = null!;
// ── Pre-generated key arrays (pointer-like, 1024-byte aligned) ──
- /// Keys for initial table fill (simulates cached addresses).
- private nint[] _residentKeys = null!;
-
- /// Additional keys not in the table, for triggering misses.
- private nint[] _externalKeys = null!;
-
- /// Zipfian-skewed lookup sequence (hot 20% accessed 80% of time).
- private nint[] _zipfianLookups = null!;
-
- /// Uniform random lookup sequence (baseline comparison).
- private nint[] _uniformLookups = null!;
+ ///
+ /// Keys for initial table fill (simulates cached addresses).
+ ///
+ private nuint[] _residentKeys = null!;
///
- /// Pre-built steady-state operation script.
- /// Each entry: positive index → lookup _residentKeys[idx],
- /// negative value → cache miss trigger (evict + insert from _externalKeys).
+ /// Pre-built steady-state operation script. Each entry: positive index → lookup
+ /// _residentKeys[idx], negative value → cache miss trigger (evict + insert from
+ /// _externalKeys).
///
private int[] _steadyStateScript = null!;
- /// Indices into _residentKeys for eviction targets (cold entries, tail region).
- private int[] _evictOrder = null!;
+ ///
+ /// Uniform random lookup sequence (baseline comparison).
+ ///
+ private nuint[] _uniformLookups = null!;
- // ── Pre-filled lookup targets ──
-
- private SwissTable _swissLookup = null!;
- private Dictionary _dictLookup = null!;
+ ///
+ /// Zipfian-skewed lookup sequence (hot 20% accessed 80% of time).
+ ///
+ private nuint[] _zipfianLookups = null!;
+ private Dictionary _dictLookup = null!;
+ private Dictionary _freshDict = null!;
// ── Fresh instances for insert/mixed (rebuilt each iteration) ──
private SwissTable _freshSwiss = null!;
- private Dictionary _freshDict = null!;
+
+ // ── Pre-filled lookup targets ──
+
+ private SwissTable _swissLookup = null!;
///
- /// Active cache line count. Matches L4 capacity range:
- /// 5 MB / 1024B ≈ 5120, 10 MB / 1024B ≈ 10240.
- /// 512 and 1024 test low-occupancy; 4096 tests near-capacity.
+ /// Active cache line count. Matches L4 capacity range: 5 MB / 1024B ≈ 5120, 10 MB /
+ /// 1024B ≈ 10240. 512 and 1024 test low-occupancy; 4096 tests near-capacity.
///
[Params(512, 1024, 4096)]
public int KeyCount;
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ _dictLookup.Clear();
+ _dictLookup.TrimExcess();
+ }
+
// ════════════════════════════════════════════════════════════
- // Setup / Teardown
+ // Insert — bulk fill with scattered pointer addresses
+ // Simulates L4 initial cache warm-up phase.
+ // ════════════════════════════════════════════════════════════
+
+ [Benchmark]
+ public void Dictionary_Insert()
+ {
+ Dictionary dict = _freshDict;
+ nuint[] keys = _residentKeys;
+ for (int i = 0; i < keys.Length; i++) {
+ dict[keys[i]] = keys[i] ^ unchecked((nuint)0xDEAD_BEEF);
+ }
+ }
+
+ // ════════════════════════════════════════════════════════════
+ // Lookup — Uniform (baseline comparison)
+ // ════════════════════════════════════════════════════════════
+
+ [Benchmark]
+ public nuint Dictionary_Lookup_Uniform()
+ {
+ nuint sum = 0;
+ nuint[] keys = _uniformLookups;
+ Dictionary dict = _dictLookup;
+ for (int i = 0; i < keys.Length; i++)
+ {
+ if (dict.TryGetValue(keys[i], out nuint value)) {
+ sum += value;
+ }
+ }
+ return sum;
+ }
+
+ // ════════════════════════════════════════════════════════════
+ // Lookup — Zipfian (L4 hot path, skewed access)
+ // 20% of cache lines receive 80% of accesses.
+ // This is the most representative L4 benchmark.
+ // ════════════════════════════════════════════════════════════
+
+ [Benchmark(Baseline = true)]
+ public nuint Dictionary_Lookup_Zipfian()
+ {
+ nuint sum = 0;
+ nuint[] keys = _zipfianLookups;
+ Dictionary dict = _dictLookup;
+ for (int i = 0; i < keys.Length; i++)
+ {
+ if (dict.TryGetValue(keys[i], out nuint value)) {
+ sum += value;
+ }
+ }
+ return sum;
+ }
+
+ // ════════════════════════════════════════════════════════════
+ // SteadyState — the KEY L4 benchmark
+ // Pre-filled table at capacity, then 4096 operations:
+ // ~95% are lookups (cache hits)
+ // ~5% are miss → evict cold entry → insert new entry → lookup new
+ // This directly simulates L4's main runtime loop.
+ // ════════════════════════════════════════════════════════════
+
+ [Benchmark]
+ public nuint Dictionary_SteadyState()
+ {
+ nuint sum = 0;
+ Dictionary dict = _freshDict;
+ nuint[] resident = _residentKeys;
+ nuint[] external = _externalKeys;
+ int[] script = _steadyStateScript;
+ int[] evict = _evictOrder;
+ int evictCursor = 0;
+ int externalCursor = 0;
+
+ for (int i = 0; i < script.Length; i++)
+ {
+ int op = script[i];
+ if (op >= 0)
+ {
+ // Cache hit: lookup existing entry
+ if (dict.TryGetValue(resident[op], out nuint value))
+ {
+ sum += value;
+ }
+ } else
+ {
+ // Cache miss: evict a cold entry, insert a new one, then lookup
+ int evictIdx = evict[evictCursor % evict.Length];
+ evictCursor++;
+ dict.Remove(resident[evictIdx]);
+
+ int extIdx = externalCursor % external.Length;
+ externalCursor++;
+ nuint newKey = external[extIdx];
+ nuint newVal = newKey ^ unchecked((nuint)0xDEAD_BEEF);
+ dict[newKey] = newVal;
+
+ if (dict.TryGetValue(newKey, out nuint v)) {
+ sum += v;
+ }
+ }
+ }
+ return sum;
+ }
+
+ [IterationSetup(Targets = [
+ nameof(SwissTable_Insert),
+ nameof(SwissTable_SteadyState),
+ nameof(Dictionary_Insert),
+ nameof(Dictionary_SteadyState)
+ ])]
+ public void IterationSetup()
+ {
+ _freshSwiss = SwissTable.Create();
+ _freshDict = new Dictionary(KeyCount);
+
+ // Pre-fill for steady-state benchmarks
+ for (int i = 0; i < KeyCount; i++)
+ {
+ nuint key = _residentKeys[i];
+ nuint val = key ^ unchecked((nuint)0xDEAD_BEEF);
+ _freshSwiss.TryAddOrSet(key, val, out _);
+ _freshDict[key] = val;
+ }
+ }
+
+ // ════════════════════════════════════════════════════════════
+ // Setup / Teardown
// ════════════════════════════════════════════════════════════
[GlobalSetup]
@@ -130,215 +278,73 @@ namespace StgSharp.Benchmark
// ── Build eviction order (cold tail entries first) ──
_evictOrder = new int[KeyCount];
- for (int i = 0; i < KeyCount; i++)
- {
+ for (int i = 0; i < KeyCount; i++) {
_evictOrder[i] = i;
}
+
// Shuffle so eviction isn't just sequential
Shuffle(_evictOrder, rng);
// ── Pre-fill lookup tables ──
_swissLookup = SwissTable.Create();
- _dictLookup = new Dictionary(KeyCount);
+ _dictLookup = new Dictionary(KeyCount);
for (int i = 0; i < KeyCount; i++)
{
- nint key = _residentKeys[i];
- nint val = key ^ unchecked((nint)0xDEAD_BEEF); // simulated cache pointer
+ nuint key = _residentKeys[i];
+ nuint val = key ^ unchecked((nuint)0xDEAD_BEEF); // simulated cache pointer
_swissLookup.TryAddOrSet(key, val, out _);
_dictLookup[key] = val;
}
}
- [GlobalCleanup]
- public void Cleanup()
- {
- _dictLookup.Clear();
- _dictLookup.TrimExcess();
- }
-
- [IterationSetup(Targets = [
- nameof(SwissTable_Insert),
- nameof(SwissTable_SteadyState),
- nameof(Dictionary_Insert),
- nameof(Dictionary_SteadyState)
- ])]
- public void IterationSetup()
- {
- _freshSwiss = SwissTable.Create();
- _freshDict = new Dictionary(KeyCount);
-
- // Pre-fill for steady-state benchmarks
- for (int i = 0; i < KeyCount; i++)
- {
- nint key = _residentKeys[i];
- nint val = key ^ unchecked((nint)0xDEAD_BEEF);
- _freshSwiss.TryAddOrSet(key, val, out _);
- _freshDict[key] = val;
- }
- }
-
- // ════════════════════════════════════════════════════════════
- // Lookup — Zipfian (L4 hot path, skewed access)
- // 20% of cache lines receive 80% of accesses.
- // This is the most representative L4 benchmark.
- // ════════════════════════════════════════════════════════════
-
- [Benchmark(Baseline = true)]
- public nint Dictionary_Lookup_Zipfian()
- {
- nint sum = 0;
- nint[] keys = _zipfianLookups;
- Dictionary dict = _dictLookup;
- for (int i = 0; i < keys.Length; i++)
- {
- if (dict.TryGetValue(keys[i], out nint value))
- {
- sum += value;
- }
- }
- return sum;
- }
-
- [Benchmark]
- public nint SwissTable_Lookup_Zipfian()
- {
- nint sum = 0;
- nint[] keys = _zipfianLookups;
- SwissTable swiss = _swissLookup;
- for (int i = 0; i < keys.Length; i++)
- {
- if (swiss.TryGet(keys[i], out nint value))
- {
- sum += value;
- }
- }
- return sum;
- }
-
- // ════════════════════════════════════════════════════════════
- // Lookup — Uniform (baseline comparison)
- // ════════════════════════════════════════════════════════════
-
- [Benchmark]
- public nint Dictionary_Lookup_Uniform()
- {
- nint sum = 0;
- nint[] keys = _uniformLookups;
- Dictionary dict = _dictLookup;
- for (int i = 0; i < keys.Length; i++)
- {
- if (dict.TryGetValue(keys[i], out nint value))
- {
- sum += value;
- }
- }
- return sum;
- }
-
- [Benchmark]
- public nint SwissTable_Lookup_Uniform()
- {
- nint sum = 0;
- nint[] keys = _uniformLookups;
- SwissTable swiss = _swissLookup;
- for (int i = 0; i < keys.Length; i++)
- {
- if (swiss.TryGet(keys[i], out nint value))
- {
- sum += value;
- }
- }
- return sum;
- }
-
- // ════════════════════════════════════════════════════════════
- // Insert — bulk fill with scattered pointer addresses
- // Simulates L4 initial cache warm-up phase.
- // ════════════════════════════════════════════════════════════
-
- [Benchmark]
- public void Dictionary_Insert()
- {
- Dictionary dict = _freshDict;
- nint[] keys = _residentKeys;
- for (int i = 0; i < keys.Length; i++)
- {
- dict[keys[i]] = keys[i] ^ unchecked((nint)0xDEAD_BEEF);
- }
- }
-
[Benchmark]
public void SwissTable_Insert()
{
SwissTable swiss = _freshSwiss;
- nint[] keys = _residentKeys;
- for (int i = 0; i < keys.Length; i++)
- {
- swiss.TryAddOrSet(keys[i], keys[i] ^ unchecked((nint)0xDEAD_BEEF), out _);
+ nuint[] keys = _residentKeys;
+ for (int i = 0; i < keys.Length; i++) {
+ swiss.TryAddOrSet(keys[i], keys[i] ^ unchecked((nuint)0xDEAD_BEEF), out _);
}
}
- // ════════════════════════════════════════════════════════════
- // SteadyState — the KEY L4 benchmark
- // Pre-filled table at capacity, then 4096 operations:
- // ~95% are lookups (cache hits)
- // ~5% are miss → evict cold entry → insert new entry → lookup new
- // This directly simulates L4's main runtime loop.
- // ════════════════════════════════════════════════════════════
-
[Benchmark]
- public nint Dictionary_SteadyState()
+ public nuint SwissTable_Lookup_Uniform()
{
- nint sum = 0;
- Dictionary dict = _freshDict;
- nint[] resident = _residentKeys;
- nint[] external = _externalKeys;
- int[] script = _steadyStateScript;
- int[] evict = _evictOrder;
- int evictCursor = 0;
- int externalCursor = 0;
-
- for (int i = 0; i < script.Length; i++)
+ nuint sum = 0;
+ nuint[] keys = _uniformLookups;
+ SwissTable swiss = _swissLookup;
+ for (int i = 0; i < keys.Length; i++)
{
- int op = script[i];
- if (op >= 0)
- {
- // Cache hit: lookup existing entry
- if (dict.TryGetValue(resident[op], out nint value))
- {
- sum += value;
- }
- }
- else
- {
- // Cache miss: evict a cold entry, insert a new one, then lookup
- int evictIdx = evict[evictCursor % evict.Length];
- evictCursor++;
- dict.Remove(resident[evictIdx]);
-
- int extIdx = externalCursor % external.Length;
- externalCursor++;
- nint newKey = external[extIdx];
- nint newVal = newKey ^ unchecked((nint)0xDEAD_BEEF);
- dict[newKey] = newVal;
-
- if (dict.TryGetValue(newKey, out nint v))
- {
- sum += v;
- }
+ if (swiss.TryGet(keys[i], out nuint value)) {
+ sum += value;
}
}
return sum;
}
[Benchmark]
- public nint SwissTable_SteadyState()
+ public nuint SwissTable_Lookup_Zipfian()
{
- nint sum = 0;
+ nuint sum = 0;
+ nuint[] keys = _zipfianLookups;
+ SwissTable swiss = _swissLookup;
+ for (int i = 0; i < keys.Length; i++)
+ {
+ if (swiss.TryGet(keys[i], out nuint value)) {
+ sum += value;
+ }
+ }
+ return sum;
+ }
+
+ [Benchmark]
+ public nuint SwissTable_SteadyState()
+ {
+ nuint sum = 0;
SwissTable swiss = _freshSwiss;
- nint[] resident = _residentKeys;
- nint[] external = _externalKeys;
+ nuint[] resident = _residentKeys;
+ nuint[] external = _externalKeys;
int[] script = _steadyStateScript;
int[] evict = _evictOrder;
int evictCursor = 0;
@@ -350,12 +356,11 @@ namespace StgSharp.Benchmark
if (op >= 0)
{
// Cache hit: lookup existing entry
- if (swiss.TryGet(resident[op], out nint value))
+ if (swiss.TryGet(resident[op], out nuint value))
{
sum += value;
}
- }
- else
+ } else
{
// Cache miss: evict a cold entry, insert a new one, then lookup
int evictIdx = evict[evictCursor % evict.Length];
@@ -364,12 +369,11 @@ namespace StgSharp.Benchmark
int extIdx = externalCursor % external.Length;
externalCursor++;
- nint newKey = external[extIdx];
- nint newVal = newKey ^ unchecked((nint)0xDEAD_BEEF);
+ nuint newKey = external[extIdx];
+ nuint newVal = newKey ^ unchecked((nuint)0xDEAD_BEEF);
swiss.TryAddOrSet(newKey, newVal, out _);
- if (swiss.TryGet(newKey, out nint v))
- {
+ if (swiss.TryGet(newKey, out nuint v)) {
sum += v;
}
}
@@ -378,21 +382,20 @@ namespace StgSharp.Benchmark
}
// ════════════════════════════════════════════════════════════
- // Data generation helpers
+ // Data generation helpers
// ════════════════════════════════════════════════════════════
///
- /// Generate unique pointer-like keys.
- /// Each key = HeapBase + randomOffset * CacheLineSize,
- /// simulating HLSF-allocated 1024-byte aligned addresses
+ /// Generate unique pointer-like keys. Each key = HeapBase +
+ /// randomOffset * CacheLineSize, simulating HLSF-allocated 1024-byte aligned addresses
/// scattered across a ~1 GB virtual address range.
///
- private static nint[] GeneratePointerKeys(
- int count,
- Random rng
+ private static nuint[] GeneratePointerKeys(
+ int count,
+ Random rng
)
{
- nint[] keys = new nint[count];
+ nuint[] keys = new nuint[count];
HashSet used = new HashSet(count);
for (int i = 0; i < count; i++)
{
@@ -402,61 +405,15 @@ namespace StgSharp.Benchmark
// Random offset within ~1M cache lines (1 GB range)
offset = (long)rng.Next(0, 1 << 20) * CacheLineSize;
} while (!used.Add(offset));
- keys[i] = (nint)(HeapBase + offset);
+ keys[i] = (nuint)(HeapBase + offset);
}
return keys;
}
///
- /// Generate Zipfian-skewed lookup sequence:
- /// 80% of accesses hit the hottest 20% of keys (sorted by index as "hotness proxy"),
- /// 20% of accesses hit the remaining 80%.
- /// This models matrix computation where a small panel working set is reused heavily.
- ///
- private static nint[] GenerateZipfianLookups(
- nint[] sourceKeys,
- int count,
- Random rng
- )
- {
- int hotSetSize = Math.Max(1, sourceKeys.Length / 5);
- nint[] lookups = new nint[count];
- for (int i = 0; i < count; i++)
- {
- if (rng.NextDouble() < 0.8)
- {
- lookups[i] = sourceKeys[rng.Next(0, hotSetSize)];
- }
- else
- {
- lookups[i] = sourceKeys[rng.Next(hotSetSize, sourceKeys.Length)];
- }
- }
- return lookups;
- }
-
- ///
- /// Generate uniform random lookup sequence (baseline for comparison with Zipfian).
- ///
- private static nint[] GenerateUniformLookups(
- nint[] sourceKeys,
- int count,
- Random rng
- )
- {
- nint[] lookups = new nint[count];
- for (int i = 0; i < count; i++)
- {
- lookups[i] = sourceKeys[rng.Next(0, sourceKeys.Length)];
- }
- return lookups;
- }
-
- ///
- /// Generate a steady-state operation script.
- /// Each entry: non-negative value = index into _residentKeys (lookup/hit),
- /// negative value (-1) = cache miss trigger (evict → insert → lookup).
- /// Lookup indices are Zipfian-skewed.
+ /// Generate a steady-state operation script. Each entry: non-negative value = index into
+ /// _residentKeys (lookup/hit), negative value (-1) = cache miss trigger (evict → insert →
+ /// lookup). Lookup indices are Zipfian-skewed.
///
private static int[] GenerateSteadyStateScript(
int keyCount,
@@ -472,15 +429,13 @@ namespace StgSharp.Benchmark
if (rng.NextDouble() < missRate)
{
script[i] = -1; // miss
- }
- else
+ } else
{
// Zipfian hit
if (rng.NextDouble() < 0.8)
{
script[i] = rng.Next(0, hotSetSize);
- }
- else
+ } else
{
script[i] = rng.Next(hotSetSize, keyCount);
}
@@ -489,7 +444,51 @@ namespace StgSharp.Benchmark
return script;
}
- /// Fisher-Yates shuffle.
+ ///
+ /// Generate uniform random lookup sequence (baseline for comparison with Zipfian).
+ ///
+ private static nuint[] GenerateUniformLookups(
+ nuint[] sourceKeys,
+ int count,
+ Random rng
+ )
+ {
+ nuint[] lookups = new nuint[count];
+ for (int i = 0; i < count; i++) {
+ lookups[i] = sourceKeys[rng.Next(0, sourceKeys.Length)];
+ }
+ return lookups;
+ }
+
+ ///
+ /// Generate Zipfian-skewed lookup sequence: 80% of accesses hit the hottest 20% of keys
+ /// (sorted by index as "hotness proxy"), 20% of accesses hit the remaining 80%. This
+ /// models matrix computation where a small panel working set is reused heavily.
+ ///
+ private static nuint[] GenerateZipfianLookups(
+ nuint[] sourceKeys,
+ int count,
+ Random rng
+ )
+ {
+ int hotSetSize = Math.Max(1, sourceKeys.Length / 5);
+ nuint[] lookups = new nuint[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (rng.NextDouble() < 0.8)
+ {
+ lookups[i] = sourceKeys[rng.Next(0, hotSetSize)];
+ } else
+ {
+ lookups[i] = sourceKeys[rng.Next(hotSetSize, sourceKeys.Length)];
+ }
+ }
+ return lookups;
+ }
+
+ ///
+ /// Fisher-Yates shuffle.
+ ///
private static void Shuffle(
int[] array,
Random rng
@@ -503,4 +502,4 @@ namespace StgSharp.Benchmark
}
}
-}
\ No newline at end of file
+}
diff --git a/StgSharp.Common/Collections/SwissTable.X86.cs b/StgSharp.Common/Collections/SwissTable.X86.cs
index 6b009f92..cf1da9b9 100644
--- a/StgSharp.Common/Collections/SwissTable.X86.cs
+++ b/StgSharp.Common/Collections/SwissTable.X86.cs
@@ -39,10 +39,10 @@ namespace StgSharp.Collections
{
public bool TryFind(
- nint key,
+ nuint key,
uint hash,
Bucket* bucket,
- out nint value
+ out nuint value
)
{
byte ctrl = (byte)((hash >> EntryMaskWidth) & 0x7F);
@@ -66,10 +66,10 @@ namespace StgSharp.Collections
}
public bool TryRemove(
- nint key,
+ nuint key,
uint hash,
Bucket* bucket,
- out nint value
+ out nuint value
)
{
byte ctrl = (byte)((hash >> EntryMaskWidth) & 0x7F);
@@ -97,10 +97,10 @@ namespace StgSharp.Collections
}
public bool TrySet(
- nint key,
+ nuint key,
uint hash,
Bucket* bucket,
- nint value,
+ nuint value,
out bool isNewKey
)
{
diff --git a/StgSharp.Common/Collections/SwissTable.cs b/StgSharp.Common/Collections/SwissTable.cs
index 96e41d46..c5e40ee7 100644
--- a/StgSharp.Common/Collections/SwissTable.cs
+++ b/StgSharp.Common/Collections/SwissTable.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="SwissTable"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -37,7 +37,7 @@ namespace StgSharp.Collections
///
///
///
- public sealed unsafe partial class SwissTable
+ public sealed unsafe partial class SwissTable : IDisposable
{
private const int BucketCount = 1 << EntryMaskWidth;
@@ -46,11 +46,19 @@ namespace StgSharp.Collections
private const int EntryMaskWidth = 10; // 1024 entries total, 16 per bucket
public const int MaxCapacity = BucketCount * BucketSize; // 256 entries per bucket, 1024 buckets
- private readonly Entry* _entries;
+ public static readonly int RequestedNativeMemorySize = Unsafe.SizeOf();
- private SwissTable(Entry* buffer)
+ private readonly Entry* _entries;
+ private readonly Action _freeHandle;
+ private bool _disposed;
+
+ private SwissTable(
+ Entry* buffer,
+ Action freeHandle
+ )
{
_entries = buffer;
+ _freeHandle = freeHandle;
}
public static SwissTable Create()
@@ -61,10 +69,24 @@ namespace StgSharp.Collections
// Set all control bytes to 0xFF (empty)
Unsafe.InitBlock(&(*buffer)[i].Control, 0xFF, BucketSize);
}
- return new SwissTable(buffer);
+ return new SwissTable(buffer, handle => NativeMemory.AlignedFree((void*)handle));
}
- public bool TryAddOrSet(nint key, nint value, out bool isNewKey)
+ public void Dispose()
+ {
+ if (_disposed) {
+ return;
+ }
+ _disposed = true;
+ _freeHandle((nuint)_entries);
+ GC.SuppressFinalize(this);
+ }
+
+ public bool TryAddOrSet(
+ nuint key,
+ nuint value,
+ out bool isNewKey
+ )
{
uint hash = BitOperations.Crc32C(CrcMagic, (ulong)key);
ushort entry = (ushort)(hash & (BucketCount - 1));
@@ -72,9 +94,12 @@ namespace StgSharp.Collections
return IBucketScanner.Current.TrySet(key, hash, bucket, value, out isNewKey);
}
- public bool TryGet(nint key, out nint value)
+ public bool TryGet(
+ nuint key,
+ out nuint value
+ )
{
- uint hash = BitOperations.Crc32C(CrcMagic, (ulong)key);
+ uint hash = BitOperations.Crc32C(CrcMagic, key);
ushort entry = (ushort)(hash & (BucketCount - 1));
Bucket* bucket = &(*_entries)[entry];
return IBucketScanner.Current.TryFind(key, hash, bucket, out value);
@@ -87,7 +112,10 @@ namespace StgSharp.Collections
*/
}
- public bool TryRemove(nint key, out nint value)
+ public bool TryRemove(
+ nuint key,
+ out nuint value
+ )
{
uint hash = BitOperations.Crc32C(CrcMagic, (ulong)key);
ushort entry = (ushort)(hash & (BucketCount - 1));
@@ -95,9 +123,23 @@ namespace StgSharp.Collections
return IBucketScanner.Current.TryRemove(key, hash, bucket, out value);
}
+ internal static SwissTable Create(
+ nuint bufferPtr,
+ Action FreeHandle
+ )
+ {
+ Entry* buffer = (Entry*)bufferPtr;
+ for (int i = 0; i < BucketCount; i++)
+ {
+ // Set all control bytes to 0xFF (empty)
+ Unsafe.InitBlock(&(*buffer)[i].Control, 0xFF, BucketSize);
+ }
+ return new SwissTable(buffer, FreeHandle);
+ }
+
~SwissTable()
{
- NativeMemory.AlignedFree(_entries);
+ Dispose();
}
[InlineArray(BucketCount)]
@@ -122,11 +164,27 @@ namespace StgSharp.Collections
static IBucketScanner Current { get; } = Create();
- bool TryFind(nint key, uint Hash, Bucket* bucket, out nint value);
+ bool TryFind(
+ nuint key,
+ uint Hash,
+ Bucket* bucket,
+ out nuint value
+ );
- unsafe bool TryRemove(nint key, uint hash, Bucket* bucket, out nint value);
+ unsafe bool TryRemove(
+ nuint key,
+ uint hash,
+ Bucket* bucket,
+ out nuint value
+ );
- bool TrySet(nint key, uint hash, Bucket* bucket, nint value, out bool isNewKey);
+ bool TrySet(
+ nuint key,
+ uint hash,
+ Bucket* bucket,
+ nuint value,
+ out bool isNewKey
+ );
private static IBucketScanner Create()
{
@@ -156,7 +214,7 @@ namespace StgSharp.Collections
private struct BucketKeyStorage
{
- public nint value;
+ public nuint value;
}
@@ -164,7 +222,7 @@ namespace StgSharp.Collections
private struct BucketValueStorage
{
- public nint value;
+ public nuint value;
}
diff --git a/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs b/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs
index b3988284..50788526 100644
--- a/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs
+++ b/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="ConcurrentBufferStack"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -40,7 +40,9 @@ namespace StgSharp.Mathematics.Memory
// For BufferStack collection builder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static BufferStack Create(ReadOnlySpan span) where T: unmanaged
+ public static BufferStack Create(
+ ReadOnlySpan span
+ ) where T : unmanaged
{
if (span.IsEmpty)
{
@@ -58,7 +60,9 @@ namespace StgSharp.Mathematics.Memory
// For ConcurrentBufferStack collection builder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ConcurrentBufferStack CreateConcurrent(ReadOnlySpan span) where T: unmanaged
+ public static ConcurrentBufferStack CreateConcurrent(
+ ReadOnlySpan span
+ ) where T : unmanaged
{
if (span.IsEmpty)
{
@@ -76,8 +80,10 @@ namespace StgSharp.Mathematics.Memory
}
- [CollectionBuilder(builderType: typeof(BufferStackBuilder), methodName: nameof(BufferStackBuilder.CreateConcurrent))]
- public unsafe class ConcurrentBufferStack : IDisposable where T: unmanaged
+ [CollectionBuilder(
+ builderType: typeof(BufferStackBuilder),
+ methodName: nameof(BufferStackBuilder.CreateConcurrent))]
+ public unsafe class ConcurrentBufferStack : IDisposable where T : unmanaged
{
private byte* _buffer;
@@ -89,11 +95,11 @@ namespace StgSharp.Mathematics.Memory
AllocateBuffer(4);
}
- public ConcurrentBufferStack(int capacity)
+ public ConcurrentBufferStack(
+ int capacity
+ )
{
- if (capacity <= 0) {
- throw new ArgumentOutOfRangeException(nameof(capacity));
- }
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
AllocateBuffer(capacity);
}
@@ -161,7 +167,9 @@ namespace StgSharp.Mathematics.Memory
}
}
- public void Push(T item)
+ public void Push(
+ T item
+ )
{
while (true)
{
@@ -236,7 +244,9 @@ namespace StgSharp.Mathematics.Memory
}
}
- public bool TryPop(out T value)
+ public bool TryPop(
+ out T value
+ )
{
while (true)
{
@@ -269,7 +279,9 @@ namespace StgSharp.Mathematics.Memory
// Internal method for efficient bulk initialization
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal unsafe void InitializeFromSpan(ReadOnlySpan span)
+ internal unsafe void InitializeFromSpan(
+ ReadOnlySpan span
+ )
{
if (span.IsEmpty) {
return;
@@ -287,7 +299,9 @@ namespace StgSharp.Mathematics.Memory
Volatile.Write(ref _count, span.Length);
}
- private void AllocateBuffer(int capacity)
+ private void AllocateBuffer(
+ int capacity
+ )
{
nuint size = (nuint)sizeof(Header) + (nuint)capacity * (nuint)sizeof(T);
_buffer = (byte*)NativeMemory.Alloc(size);
diff --git a/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Alloc.cs b/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Alloc.cs
index 8f5f5884..cbce7c6f 100644
--- a/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Alloc.cs
+++ b/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Alloc.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="HLSFAllocator.Alloc"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -34,7 +34,9 @@ namespace StgSharp.Mathematics.Memory
public unsafe partial class HybridLayerSegregatedFitAllocator
{
- public hlsfHandle Alloc(long size)
+ public partial hlsfHandle Alloc(
+ long size
+ )
{
if (size > _maxSize) {
throw new InvalidOperationException("Requested size exceeds maximum allocatable size.");
@@ -91,7 +93,10 @@ namespace StgSharp.Mathematics.Memory
///
///
///
- private void SliceMemory(Entry* e, long sizeToSlice)
+ private void SliceMemory(
+ Entry* e,
+ long sizeToSlice
+ )
{
Entry* next = e;
long remain = sizeToSlice; // remained size to slice
@@ -189,7 +194,9 @@ namespace StgSharp.Mathematics.Memory
///
/// True if allocation succeeded
///
- private bool TryAllocateNew32MBBlock(out Entry* handle)
+ private bool TryAllocateNew32MBBlock(
+ out Entry* handle
+ )
{
nuint pos = _spareMemory->Position;
long remain = _spareMemory->Size;
@@ -219,7 +226,11 @@ namespace StgSharp.Mathematics.Memory
///
/// Helper method for allocation - converts BucketNode to Entry
///
- private bool TryPopLevelForAllocation(int levelIndex, int sizeIndex, out Entry* entry)
+ private bool TryPopLevelForAllocation(
+ int levelIndex,
+ int sizeIndex,
+ out Entry* entry
+ )
{
if (TryPopLevel(levelIndex, sizeIndex, out BucketNode* bucketNode))
{
diff --git a/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Free.cs b/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Free.cs
index cadbfe06..85733fd4 100644
--- a/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Free.cs
+++ b/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.Free.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="HLSFAllocator.Free"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -55,7 +55,7 @@ namespace StgSharp.Mathematics.Memory
/// Minimal collect: linear scan, merge contiguous empty Blocks into one, push merged head
/// back to bucket. Caller-responsible design: no slicing or redistribution.
///
- public void Collect()
+ public partial void Collect()
{
Entry* cur = _spareMemory->NextNear;
long size = 0;
@@ -90,7 +90,10 @@ namespace StgSharp.Mathematics.Memory
/// Handle to the allocated memory block
///
///
- public void Free(hlsfHandle handle, FreePolicy collectPolicy = FreePolicy.NoCollect)
+ public void Free(
+ hlsfHandle handle,
+ FreePolicy collectPolicy = FreePolicy.NoCollect
+ )
{
Entry* e = handle.EntryHandle;
if (e == null) {
diff --git a/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.cs b/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.cs
index 97b4149e..ff0bfdc8 100644
--- a/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.cs
+++ b/StgSharp.Common/HighPerformance/Memory/HLSFAllocator.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="HLSFAllocator"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,6 +30,7 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
+using hlsfHandle = StgSharp.Mathematics.Memory.HybridLayerSegregatedFitAllocationHandle;
namespace StgSharp.Mathematics.Memory
{
@@ -48,17 +49,19 @@ namespace StgSharp.Mathematics.Memory
private readonly nuint _size;
private readonly SlabAllocator _entries;
- public HybridLayerSegregatedFitAllocator(nuint byteSize)
+ public HybridLayerSegregatedFitAllocator(
+ nuint byteSize
+ )
{
if (byteSize < 64) {
throw new InvalidOperationException();
}
- _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
+ _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
_size = byteSize;
m_Buffer = (byte *)NativeMemory.AlignedAlloc(byteSize, 16);
_align = 16;
- _bucketHeads = new BucketNode *[_levelSizeArray.Length * 3 + 1];
+ _bucketHeads = new BucketNode*[_levelSizeArray.Length * 3 + 1];
_spareMemory = (Entry *)_entries.Allocate();
_spareMemory->State = EntryState.Spare;
@@ -70,9 +73,12 @@ namespace StgSharp.Mathematics.Memory
_maxSize = 32U * 1024U * 1024U;
}
- public HybridLayerSegregatedFitAllocator(nuint byteSize, uint maxBlockSize)
+ public HybridLayerSegregatedFitAllocator(
+ nuint byteSize,
+ uint maxBlockSize
+ )
{
- _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
+ _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
m_Buffer = (byte *)NativeMemory.AlignedAlloc(byteSize, 16);
_align = 16;
_size = byteSize;
@@ -97,13 +103,16 @@ namespace StgSharp.Mathematics.Memory
_maxSize = maxBlockSize;
}
- public HybridLayerSegregatedFitAllocator(nuint byteSize, int align)
+ public HybridLayerSegregatedFitAllocator(
+ nuint byteSize,
+ int align
+ )
{
if (align < 16 || (align & (align - 1)) != 0) {
throw new ArgumentException(
"Alignment must be a power of two and at least 16 bytes.");
}
- _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
+ _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
m_Buffer = (byte *)NativeMemory.AlignedAlloc(byteSize, (nuint)align);
_align = align;
_size = byteSize;
@@ -120,13 +129,17 @@ namespace StgSharp.Mathematics.Memory
_maxSize = 32U * 1024U * 1024U;
}
- public HybridLayerSegregatedFitAllocator(nuint byteSize, int align, long maxBlockSize)
+ public HybridLayerSegregatedFitAllocator(
+ nuint byteSize,
+ int align,
+ long maxBlockSize
+ )
{
if (align < 16 || (align & (align - 1)) != 0) {
throw new ArgumentException(
"Alignment must be a power of two and at least 16 bytes.");
}
- _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
+ _entries = SlabAllocator.Create(64, SlabBufferLayout.Chunked, false);
m_Buffer = (byte*)NativeMemory.AlignedAlloc(byteSize, (nuint)align);
_align = align;
_size = byteSize;
@@ -151,13 +164,21 @@ namespace StgSharp.Mathematics.Memory
_maxSize = maxBlockSize;
}
+ public partial hlsfHandle Alloc(
+ long size
+ );
+
+ public partial void Collect();
+
public void Dispose()
{
Dispose(disposing:true);
GC.SuppressFinalize(this);
}
- protected virtual void Dispose(bool disposing)
+ protected virtual void Dispose(
+ bool disposing
+ )
{
if (!disposedValue)
{
diff --git a/StgSharp.Common/HighPerformance/Memory/L4.CommandQueue.cs b/StgSharp.Common/HighPerformance/Memory/L4.CommandQueue.cs
index 060caca0..8aa5101e 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.CommandQueue.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.CommandQueue.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="L4.CommandQueue"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -37,7 +37,7 @@ namespace StgSharp.HighPerformance.Memory
public unsafe partial class L4
{
- private CommandQueue _commandQueue;
+ private readonly CommandQueue _commandQueue;
private struct CommandQueue
{
@@ -56,7 +56,9 @@ namespace StgSharp.HighPerformance.Memory
///
/// True if there is a command to execute, otherwise false
///
- public bool TryDequeue(out nint command)
+ public bool TryDequeue(
+ out nint command
+ )
{
if (_curRead >= 64)
{
diff --git a/StgSharp.Common/HighPerformance/Memory/L4.EvictionRing.cs b/StgSharp.Common/HighPerformance/Memory/L4.EvictionRing.cs
index 9cacfacf..55bec762 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.EvictionRing.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.EvictionRing.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="L4.EvictionRing"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -45,14 +45,16 @@ namespace StgSharp.HighPerformance.Memory
private nint[] _bucket;
- public void AddNode(EvictionRingNode* node)
+ public void AddNode(
+ CacheLineHead* node
+ )
{
nint address = node->_address;
int hash = (int)(address % (nint)_bucket.Length);
nint head = _bucket[hash];
if (head != 0)
{
- EvictionRingNode* headNode = (EvictionRingNode*)head;
+ CacheLineHead* headNode = (CacheLineHead*)head;
headNode->_prev = node;
node->_next = headNode;
}
@@ -62,17 +64,23 @@ namespace StgSharp.HighPerformance.Memory
}
[StructLayout(LayoutKind.Sequential)]
- private unsafe struct EvictionRingNode
+ private unsafe struct CacheLineHead
{
- public EvictionRingNode* _next;
- public EvictionRingNode* _prev;
- public int _level;
- public int _refCount;
- public nint _address;
+ internal CacheLineHead* _next;
+ internal CacheLineHead* _prev;
+ internal int _level;
+ internal int _refCount;
+ internal nuint _address;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void IncrementRefCount()
+ public void Unuse()
+ {
+ _ = Interlocked.Decrement(ref _refCount);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Use()
{
_ = Interlocked.Increment(ref _refCount);
}
diff --git a/StgSharp.Common/HighPerformance/Memory/L4.cs b/StgSharp.Common/HighPerformance/Memory/L4.cs
index 8b093fe7..add2146e 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="L4"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,25 +30,75 @@ using StgSharp.Mathematics.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
+using System.Xml.Serialization;
namespace StgSharp.HighPerformance.Memory
{
- public unsafe partial class L4
+ public unsafe partial class L4 : IDisposable
{
- private readonly byte* _buffer;
+ private const int CacheLineCount = 8192;
+
+ private bool _disposed;
+
+ private readonly nuint _baseAddress;
+
+ private readonly SlabAllocator _cacheLineAllocator;
+
+ private readonly SlabAllocator _entryAllocator;
+
private readonly SwissTable _map;
- private SlabAllocator EntryAllocator { get; set; }
+ public L4()
+ {
+ int cacheSize = Unsafe.SizeOf() * CacheLineCount;
+ int entrySize = Unsafe.SizeOf() * CacheLineCount;
+ int mapSize = SwissTable.RequestedNativeMemorySize;
+ _baseAddress = (nuint)NativeMemory.AlignedAlloc((nuint)(cacheSize + entrySize + mapSize), 16);
+ nuint _buffer = _baseAddress;
+ _cacheLineAllocator = SlabAllocator.FromBuffer(_buffer, CacheLineCount, _ => { });
+ _entryAllocator = SlabAllocator.FromBuffer(_buffer + (nuint)cacheSize, CacheLineCount, _ => { });
+ _map = SwissTable.Create(_baseAddress + (nuint)(cacheSize + entrySize), _ => { });
+ }
- private SlabAllocator CacheLineAllocator { get; set; }
+ [System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "Design",
+ "CA1063:Implement IDisposable correctly",
+ Justification = """
+ Only owns native memory via NativeMemory.AlignedFree();
+ no managed IDisposable fields.
+ Dispose(bool) pattern is unnecessary.
+ """)]
+ public void Dispose()
+ {
+ if (_disposed) {
+ return;
+ }
+ _disposed = true;
+ NativeMemory.AlignedFree((void*)_baseAddress);
+ GC.SuppressFinalize(this);
+ }
- public struct CacheLine
+ public CacheLine Map(
+ nuint origin
+ )
+ {
+ // Placeholder implementation; actual mapping logic would go here.
+ if (_map.TryGet(origin, out nuint entryHandle))
+ {
+ CacheLineHead* entry = (CacheLineHead*)entryHandle;
+ return new CacheLine(entryHandle);
+ }
+ return origin;
+ }
+
+ private struct CacheLineData
{
- public fixed byte Data[64];
+ private fixed byte Data[8192];
}
diff --git a/StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs b/StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs
new file mode 100644
index 00000000..fb3a6d94
--- /dev/null
+++ b/StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs
@@ -0,0 +1,131 @@
+//-----------------------------------------------------------------------
+// -----------------------------------------------------------------------
+// file="SlabAllocator.Fixed"
+// Project: StgSharp
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+// -----------------------------------------------------------------------
+// -----------------------------------------------------------------------
+using StgSharp.Collections;
+using StgSharp.Mathematics.Memory;
+using StgSharp.Threading;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StgSharp.HighPerformance.Memory
+{
+ internal unsafe class FixedSlabAllocator : SlabAllocator where T : unmanaged
+ {
+
+ private Action _freeHandle;
+
+ private bool _disposed;
+ private CapacityFixedStack _recycle;
+ private readonly int _elementSize = Unsafe.SizeOf();
+
+ private nuint _buffer;
+
+ private nuint _Capacity, _currentIndex;
+
+ public FixedSlabAllocator(
+ nuint buffer,
+ nuint count,
+ Action freeHandle
+ )
+ {
+ _freeHandle = freeHandle;
+ _buffer = buffer;
+
+ _Capacity = count;
+ int initCount = count switch
+ {
+ <= 4 => 4,
+ > 64 => 64,
+ _ => (int)count
+ };
+ Span span = stackalloc nuint[initCount];
+ for (int i = 0; i < initCount; i++) {
+ span[i] = _buffer + (nuint)(i * _elementSize);
+ }
+ _recycle = CapacityFixedStackBuilder.Create(span);
+ _Capacity = count;
+ _currentIndex = (nuint)initCount;
+ }
+
+ public override nuint Allocate()
+ {
+ if (_recycle.TryPop(out nuint handle)) {
+ return handle;
+ }
+
+ // nuint bufferHandle = _currentBuffer;
+ nuint curIndex = _currentIndex;
+ if (curIndex >= _Capacity) {
+ return 0;
+ }
+ curIndex = _currentIndex;
+ _currentIndex += 1;
+ return _buffer + ((nuint)_elementSize * curIndex);
+ }
+
+ public override void Dispose()
+ {
+ if (_disposed) {
+ return;
+ }
+ _disposed = true;
+ _freeHandle(_buffer);
+ _recycle = null!;
+ GC.SuppressFinalize(this);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void EnterBufferReading() { }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void ExitBufferReading() { }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Free(
+ nuint index
+ )
+ {
+ _recycle.Push(index);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override SlabAllocationHandle ReadAllocation()
+ {
+ return new SlabAllocationHandle(this, false);
+ }
+
+ ~FixedSlabAllocator()
+ {
+ Dispose();
+ }
+
+ }
+}
diff --git a/StgSharp.Common/HighPerformance/Memory/SlabAllocator.cs b/StgSharp.Common/HighPerformance/Memory/SlabAllocator.cs
index b273b100..0032794e 100644
--- a/StgSharp.Common/HighPerformance/Memory/SlabAllocator.cs
+++ b/StgSharp.Common/HighPerformance/Memory/SlabAllocator.cs
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="SlabAllocator"
// Project: StgSharp
-// AuthorGroup: Nitload Space
-// Copyright (c) Nitload Space. All rights reserved.
+// AuthorGroup: Nitload
+// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -25,15 +25,14 @@
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
+using StgSharp.HighPerformance.Memory;
using System;
namespace StgSharp.Mathematics.Memory
{
- public abstract class SlabAllocator : IDisposable where T: unmanaged
+ public static class SlabAllocator
{
- public abstract nuint Allocate();
-
///
/// Creates a new SLAB allocator instance with the specified configuration.
///
@@ -78,7 +77,11 @@ namespace StgSharp.Mathematics.Memory
/// /// allocated independently, avoiding the memory relocation issues present in
/// sequential /// layouts.
///
- public static SlabAllocator Create(nuint count, SlabBufferLayout layout, bool concurrentSupport = true)
+ public static SlabAllocator Create(
+ nuint count,
+ SlabBufferLayout layout,
+ bool concurrentSupport = true
+ ) where T : unmanaged
{
if (concurrentSupport) {
return layout switch
@@ -96,13 +99,32 @@ namespace StgSharp.Mathematics.Memory
};
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SlabAllocator FromBuffer(
+ nuint buffer,
+ nuint count,
+ Action freeHandle
+ ) where T : unmanaged
+ {
+ return new FixedSlabAllocator(buffer, count, freeHandle);
+ }
+
+ }
+
+ public abstract class SlabAllocator : IDisposable where T : unmanaged
+ {
+
+ public abstract nuint Allocate();
+
public abstract void Dispose();
public abstract void EnterBufferReading();
public abstract void ExitBufferReading();
- public abstract void Free(nuint index);
+ public abstract void Free(
+ nuint index
+ );
public abstract SlabAllocationHandle ReadAllocation();