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();