// ----------------------------------------------------------------------------- // file="SlabAllocator.SequencialConcurrent" // Project: StgSharp // Copyright (c) Nitload. // SPDX-License-Identifier: MIT // ----------------------------------------------------------------------------- using Nitload.Threading; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace Nitload.Common.HighPerformance.Memory { public sealed unsafe class ConcurrentSequentialSlabAllocator : SlabAllocator where T : unmanaged { private T* _buffer; private bool _disposed; private readonly BufferExpansionLock _lock = new(); private readonly ConcurrentBufferStack _stack; private readonly int _elementSize; private nuint _highestAllocated; private ulong _capacity; public ConcurrentSequentialSlabAllocator( nuint initialCapacity ) { _elementSize = Unsafe.SizeOf(); _capacity = initialCapacity; _highestAllocated = 0; _buffer = (T*)NativeMemory.Alloc(((nuint)_capacity) * ((nuint)_elementSize)); int initCount = _capacity switch { <= 4 => 4, > 64 => 64, _ => ((int)_capacity) / 4 }; // Callback fill instead of scratch span + CreateConcurrent: avoids // the stackalloc and the copy, and sidesteps the CS0411 inference // gap. Safe without locking — not yet published to other threads. // // Note this variant stores *indices*, not addresses, so the buffer // argument is unused here; it is kept to match the shared // SpanAction shape. _stack = new(initCount); _stack.FillRange(init, 0); _highestAllocated += (nuint)initCount; static void init( Span arr, nuint _ ) { for (int i = 0; i < arr.Length; i++) { arr[i] = (nuint)i; } } } internal T* BasePointer => _buffer; public override nuint Allocate() { if (_stack.TryPop(out nuint index)) { return index; } while (true) { index = Volatile.Read(ref _highestAllocated); _lock.EnterMetaDataRead(); try { if (index >= _capacity) { _lock.ExitMetaDataRead(); _lock.EnterExpansionProcess(); try { _capacity = Volatile.Read(ref _capacity) * 2; nuint newSize = ((nuint)_capacity) * ((nuint)_elementSize); _lock.EnterBufferCopy(); try { _buffer = (T*)NativeMemory.Realloc(_buffer, newSize); } finally { _lock.ExitBufferCopy(); } } finally { _lock.ExitExpansionProcess(); } } _ = Interlocked.Increment(ref Unsafe.As(ref _highestAllocated)); return index; } finally { if (_lock.IsThreadReadingMetaData) { _lock.ExitMetaDataRead(); } } } } public override void Dispose() { if (_disposed) { return; } _disposed = true; _stack.Dispose(); NativeMemory.Free(_buffer); _lock.Dispose(); GC.SuppressFinalize(this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void EnterBufferReading() { _lock.EnterBufferRead(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void ExitBufferReading() { _lock.ExitBufferRead(); } public override void Free( nuint index ) { _stack.Push(index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override SlabAllocationHandle ReadAllocation() { EnterBufferReading(); return new SlabAllocationHandle(this, true); } ~ConcurrentSequentialSlabAllocator() { Dispose(); } } }