Refactor thread cache management in allocators

Refactored thread-local stack allocator in AllocationManager to use ThreadLocalStackPool, removing global stack pointer arrays and locks. In FreeList, replaced fixed-size cache array and maxConcurrencyLevel with a dynamic linked-list system using SharedState and CacheReclaimer for thread cache lifecycle management. Block headers now store cache pointers instead of indices. Updated allocation/free logic and tests accordingly. Bumped assembly version to 3.1.3.
This commit is contained in:
2026-05-02 16:47:50 +09:00
parent d6b4074281
commit 0265a386ba
8 changed files with 170 additions and 237 deletions

View File

@@ -135,6 +135,7 @@ internal static class JobUtility
// Lock-Free constants: State mask (low 16 bits) and RC unit (1 << 16) // Lock-Free constants: State mask (low 16 bits) and RC unit (1 << 16)
public const int STATE_MASK = 0xFFFF; public const int STATE_MASK = 0xFFFF;
public const int RC_ONE = 0x10000; public const int RC_ONE = 0x10000;
public const int RC_SHIFT = 16;
public const int JOBSTATE_INVALID = (int)JobState.Invalid & STATE_MASK; public const int JOBSTATE_INVALID = (int)JobState.Invalid & STATE_MASK;
public const int JOBSTATE_CREATED = (int)JobState.Created & STATE_MASK; public const int JOBSTATE_CREATED = (int)JobState.Created & STATE_MASK;
@@ -172,18 +173,18 @@ internal static class JobUtility
public static int ReadRefCount(ref JobInfo jobInfo) public static int ReadRefCount(ref JobInfo jobInfo)
{ {
var stateVal = Volatile.Read(ref jobInfo.state); var stateVal = Volatile.Read(ref jobInfo.state);
return stateVal >> 16; // RC is stored in the high 16 bits return stateVal >> RC_SHIFT;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetRefCount(int stateValue) public static int GetRefCount(int stateValue)
{ {
return stateValue >> 16; return stateValue >> RC_SHIFT;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReleaseRC(ref int jobState) public static int ReleaseRC(ref int jobState)
{ {
return GetRefCount(Interlocked.Add(ref jobState, -RC_ONE)); return (Interlocked.Add(ref jobState, -RC_ONE) & ~STATE_MASK) >> RC_SHIFT;
} }
} }

View File

@@ -383,6 +383,8 @@ public sealed unsafe partial class JobScheduler : IDisposable
// Release RC // Release RC
Interlocked.Add(ref depJobInfo.state, -JobUtility.RC_ONE); Interlocked.Add(ref depJobInfo.state, -JobUtility.RC_ONE);
registered = true;
break; break;
} }

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AssemblyVersion>3.1.2</AssemblyVersion> <AssemblyVersion>3.1.3</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>

View File

@@ -138,12 +138,30 @@ public static unsafe class AllocationManager
} }
} }
private class ThreadLocalStackPool
{
public MemoryPool<VirtualStack, VirtualStack.CreationOptions> pool;
public ThreadLocalStackPool(nuint stackCapacity)
{
pool = new MemoryPool<VirtualStack, VirtualStack.CreationOptions>(new VirtualStack.CreationOptions
{
reserveCapacity = stackCapacity
});
}
~ThreadLocalStackPool()
{
pool.Dispose();
}
}
internal static MemoryPool<VirtualArena, VirtualArena.CreationOptions> s_arenaAllocator; internal static MemoryPool<VirtualArena, VirtualArena.CreationOptions> s_arenaAllocator;
internal static MemoryPool<FreeList, FreeList.CreationOptions> s_freeListAllocator; internal static MemoryPool<FreeList, FreeList.CreationOptions> s_freeListAllocator;
internal static HeapAllocator* s_pHeapAllocator; internal static HeapAllocator* s_pHeapAllocator;
[ThreadStatic] [ThreadStatic]
private static MemoryPool<VirtualStack, VirtualStack.CreationOptions> t_stackAllocator; private static ThreadLocalStackPool? t_stackAllocator;
#if MHP_ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
@@ -172,52 +190,6 @@ public static unsafe class AllocationManager
private static nuint s_threadLocalStackSize; private static nuint s_threadLocalStackSize;
private static SpinLock s_stackLocker = new SpinLock(false); private static SpinLock s_stackLocker = new SpinLock(false);
private static VirtualStack** s_ppStack;
private static int s_ppStackCount;
private static int s_ppStackCapacity;
private static void EnsureThreadLocalStackInitialize()
{
if (Unsafe.IsNullRef(ref t_stackAllocator.Allocator))
{
t_stackAllocator = new MemoryPool<VirtualStack, VirtualStack.CreationOptions>(new VirtualStack.CreationOptions
{
reserveCapacity = s_threadLocalStackSize
});
var token = false;
try
{
s_stackLocker.Enter(ref token);
if (s_ppStack == null)
{
s_ppStack = (VirtualStack**)Malloc((nuint)(sizeof(VirtualStack*) * Environment.ProcessorCount));
s_ppStackCapacity = Environment.ProcessorCount;
}
if (s_ppStackCount >= s_ppStackCapacity)
{
var pOld = s_ppStack;
var newCapacity = s_ppStackCapacity * 2;
var pNew = (VirtualStack**)Realloc(pOld, (nuint)(sizeof(VirtualStack*) * newCapacity));
s_ppStack = pNew;
s_ppStackCapacity = newCapacity;
}
s_ppStack[s_ppStackCount] = (VirtualStack*)Unsafe.AsPointer(ref t_stackAllocator.Allocator);
var test = s_ppStack[s_ppStackCount];
s_ppStackCount++;
}
finally
{
if (token)
{
s_stackLocker.Exit();
}
}
}
}
public static void Initialize(AllocationManagerDesc opts) public static void Initialize(AllocationManagerDesc opts)
{ {
@@ -290,8 +262,8 @@ public static unsafe class AllocationManager
{ {
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
EnsureThreadLocalStackInitialize(); t_stackAllocator ??= new ThreadLocalStackPool(s_threadLocalStackSize);
return t_stackAllocator.Allocator.CreateScope(t_stackAllocator.AllocationHandle); return t_stackAllocator.pool.Allocator.CreateScope(t_stackAllocator.pool.AllocationHandle);
} }
/// <summary> /// <summary>
@@ -447,22 +419,6 @@ public static unsafe class AllocationManager
s_arenaAllocator.Dispose(); s_arenaAllocator.Dispose();
s_freeListAllocator.Dispose(); s_freeListAllocator.Dispose();
if (s_ppStack != null)
{
for (var i = 0; i < s_ppStackCount; i++)
{
var pStack = s_ppStack[i];
if (pStack != null)
{
pStack->Dispose();
Free(pStack);
}
}
Free(s_ppStack);
s_ppStack = null;
}
if (s_pHeapAllocator != null) if (s_pHeapAllocator != null)
{ {
Free(s_pHeapAllocator); Free(s_pHeapAllocator);

View File

@@ -19,7 +19,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
public static FreeList Create(in CreationOptions opts) public static FreeList Create(in CreationOptions opts)
{ {
return new FreeList(opts.alignment, opts.chunkSize, opts.maxConcurrencyLevel); return new FreeList(opts.alignment, opts.chunkSize);
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@@ -52,7 +52,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
public int creationLock; public int creationLock;
} }
[StructLayout(LayoutKind.Explicit, Size = 640)] [StructLayout(LayoutKind.Explicit, Size = 648)]
private struct ThreadCache private struct ThreadCache
{ {
[FieldOffset(0)] [FieldOffset(0)]
@@ -65,21 +65,60 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
// Padding to prevent false sharing on remoteFreeHead // Padding to prevent false sharing on remoteFreeHead
[FieldOffset(576)] [FieldOffset(576)]
public nint remoteFreeHead; public nint remoteFreeHead;
[FieldOffset(584)]
public ThreadCache* next;
[FieldOffset(592)]
public ThreadCache* inactiveNext;
} }
[StructLayout(LayoutKind.Explicit, Size = 16)] [StructLayout(LayoutKind.Explicit, Size = 24)]
private struct BlockHeader private struct BlockHeader
{ {
[FieldOffset(0)] [FieldOffset(0)]
public MemoryChunk* ownerChunk; public MemoryChunk* ownerChunk;
[FieldOffset(8)] [FieldOffset(8)]
public ThreadCache* ownerCache;
[FieldOffset(16)]
public uint magicNumber; public uint magicNumber;
[FieldOffset(12)] [FieldOffset(20)]
public ushort ownerCacheIndex;
[FieldOffset(14)]
public byte bucketIndex; public byte bucketIndex;
} }
[StructLayout(LayoutKind.Sequential)]
private struct SharedState
{
public int isDisposed;
public ThreadCache* headCache;
public ThreadCache* inactiveCacheHead;
}
private class CacheReclaimer
{
private readonly ThreadCache* _cache;
private readonly SharedState* _state;
public CacheReclaimer(ThreadCache* cache, SharedState* state)
{
_cache = cache;
_state = state;
}
~CacheReclaimer()
{
if (_cache != null && Volatile.Read(ref _state->isDisposed) == 0)
{
Volatile.Write(ref _cache->active, 0);
ThreadCache* current;
do
{
current = (ThreadCache*)Volatile.Read(ref *(nint*)&_state->inactiveCacheHead);
_cache->inactiveNext = current;
}
while (Interlocked.CompareExchange(ref *(nint*)&_state->inactiveCacheHead, (nint)_cache, (nint)current) != (nint)current);
}
}
}
private const byte _MAX_BUCKETS = 16; private const byte _MAX_BUCKETS = 16;
private const int _DEFAULT_MAX_CONCURRENCY_LEVEL = 1; private const int _DEFAULT_MAX_CONCURRENCY_LEVEL = 1;
private const int _OVERFLOW_CACHE_INDEX = 0; private const int _OVERFLOW_CACHE_INDEX = 0;
@@ -88,23 +127,22 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
private const uint _MAGIC_NUMBER = 0xDEADBEEF; private const uint _MAGIC_NUMBER = 0xDEADBEEF;
[ThreadStatic] [ThreadStatic]
private static ushort t_cacheIndex; private static ThreadCache* t_localCache;
[ThreadStatic] [ThreadStatic]
private static void* t_ownerId; private static void* t_ownerId;
[ThreadStatic]
private static CacheReclaimer? t_cacheReclaimer;
private void* _instanceId; private void* _instanceId;
private ThreadCache** _caches;
private DynamicArena _chunkArena; private DynamicArena _chunkArena;
private MemoryChunk* _chunks; private MemoryChunk* _chunks;
private readonly nuint _chunkSize; private readonly nuint _chunkSize;
private readonly nuint _alignment; private readonly nuint _alignment;
private readonly int _maxConcurrencyLevel;
private ushort _cacheCount;
private volatile int _disposed; private volatile int _disposed;
private volatile int _chunkCreationLock; private volatile int _chunkCreationLock;
private volatile int _cacheRegistrationLock; private volatile int _cacheRegistrationLock;
private volatile int _overflowLock;
/// <summary> /// <summary>
/// Gets the alignment requirement for allocations. /// Gets the alignment requirement for allocations.
@@ -116,18 +154,12 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
/// </summary> /// </summary>
public readonly nuint ChunkSize => _chunkSize; public readonly nuint ChunkSize => _chunkSize;
/// <summary>
/// Gets the maximum number of dedicated thread caches.
/// </summary>
public readonly int MaxConcurrencyLevel => _maxConcurrencyLevel;
/// <summary> /// <summary>
/// Initializes a new variable-size FreeList allocator with the specified parameters. /// Initializes a new variable-size FreeList allocator with the specified parameters.
/// </summary> /// </summary>
/// <param name="alignment">Alignment requirement for blocks (must be power of 2).</param> /// <param name="alignment">Alignment requirement for blocks (must be power of 2).</param>
/// <param name="chunkSize">Size of memory chunks to allocate (default: 64KB).</param> /// <param name="chunkSize">Size of memory chunks to allocate (default: 64KB).</param>
/// <param name="maxConcurrencyLevel">Maximum number of dedicated thread caches.</param> public FreeList(nuint alignment, nuint chunkSize = _DEFAULT_CHUNK_SIZE)
public FreeList(nuint alignment, nuint chunkSize = _DEFAULT_CHUNK_SIZE, int maxConcurrencyLevel = _DEFAULT_MAX_CONCURRENCY_LEVEL)
{ {
if (alignment == 0 || (alignment & (alignment - 1)) != 0) if (alignment == 0 || (alignment & (alignment - 1)) != 0)
{ {
@@ -139,56 +171,32 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
throw new ArgumentException("Chunk size must be at least 1KB", nameof(chunkSize)); throw new ArgumentException("Chunk size must be at least 1KB", nameof(chunkSize));
} }
if (maxConcurrencyLevel < 1)
{
throw new ArgumentOutOfRangeException(nameof(maxConcurrencyLevel), "Max concurrency level must be greater than zero.");
}
_alignment = alignment; _alignment = alignment;
_chunkSize = chunkSize; _chunkSize = chunkSize;
_maxConcurrencyLevel = maxConcurrencyLevel;
try try
{ {
_instanceId = Malloc((nuint)sizeof(nint)); var state = (SharedState*)Malloc((nuint)sizeof(SharedState));
state->isDisposed = 0;
state->headCache = null;
state->inactiveCacheHead = null;
_instanceId = state;
_chunks = null; _chunks = null;
_cacheCount = 0;
_disposed = 0; _disposed = 0;
_chunkCreationLock = 0; _chunkCreationLock = 0;
_cacheRegistrationLock = 0; _cacheRegistrationLock = 0;
_overflowLock = 0;
_chunkArena = new DynamicArena(1024); _chunkArena = new DynamicArena(1024);
_caches = (ThreadCache**)Malloc((nuint)sizeof(ThreadCache*) * (nuint)(maxConcurrencyLevel + 1));
for (var i = 0; i <= maxConcurrencyLevel; i++)
{
_caches[i] = null;
}
var overflowCache = CreateCacheForThread(0);
if (overflowCache == null)
{
throw new OutOfMemoryException("Failed to initialize free list overflow cache.");
}
_caches[_OVERFLOW_CACHE_INDEX] = overflowCache;
} }
catch catch
{ {
if (_instanceId != null) if (_instanceId != null)
{ {
Free(_instanceId); MemoryUtility.Free(_instanceId);
_instanceId = null; _instanceId = null;
} }
if (_caches != null)
{
Free(_caches);
_caches = null;
}
_chunkArena.Dispose(); _chunkArena.Dispose();
throw; throw;
@@ -268,76 +276,85 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly ThreadCache* GetOverflowCache()
{
return _caches[_OVERFLOW_CACHE_INDEX];
}
private ThreadCache* RegisterThreadCache() private ThreadCache* RegisterThreadCache()
{ {
while (Interlocked.CompareExchange(ref _cacheRegistrationLock, 1, 0) != 0) if (_instanceId == null || _disposed != 0)
{ {
Thread.SpinWait(1); return null;
} }
try var state = (SharedState*)_instanceId;
if (Volatile.Read(ref state->isDisposed) != 0)
{ {
if (t_ownerId == _instanceId && t_cacheIndex > 0 && t_cacheIndex <= _cacheCount) return null;
}
var threadId = Environment.CurrentManagedThreadId;
ThreadCache* cacheToUse = null;
while (true)
{
cacheToUse = (ThreadCache*)Volatile.Read(ref *(nint*)&state->inactiveCacheHead);
if (cacheToUse == null)
{ {
return _caches[t_cacheIndex]; break;
} }
if (_cacheCount >= _maxConcurrencyLevel) var nextInactive = cacheToUse->inactiveNext;
if (Interlocked.CompareExchange(ref *(nint*)&state->inactiveCacheHead, (nint)nextInactive, (nint)cacheToUse) == (nint)cacheToUse)
{ {
t_ownerId = _instanceId; cacheToUse->threadId = threadId;
t_cacheIndex = _OVERFLOW_CACHE_INDEX; Volatile.Write(ref cacheToUse->active, 1);
return GetOverflowCache(); break;
}
}
if (cacheToUse == null)
{
while (Interlocked.CompareExchange(ref _cacheRegistrationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
} }
var threadId = Environment.CurrentManagedThreadId; try
var cache = CreateCacheForThread(threadId);
if (cache == null)
{ {
t_ownerId = _instanceId; cacheToUse = CreateCacheForThread(threadId);
t_cacheIndex = _OVERFLOW_CACHE_INDEX; if (cacheToUse != null)
return GetOverflowCache(); {
cacheToUse->next = state->headCache;
state->headCache = cacheToUse;
}
} }
finally
{
Interlocked.Exchange(ref _cacheRegistrationLock, 0);
}
}
_cacheCount++; if (cacheToUse != null)
_caches[_cacheCount] = cache; {
t_ownerId = _instanceId; t_ownerId = _instanceId;
t_cacheIndex = _cacheCount; t_localCache = cacheToUse;
return cache; t_cacheReclaimer = new CacheReclaimer(cacheToUse, state);
}
finally
{
Interlocked.Exchange(ref _cacheRegistrationLock, 0);
} }
return cacheToUse;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private ThreadCache* GetCurrentCache() private ThreadCache* GetCurrentCache()
{ {
if (t_ownerId == _instanceId) if (t_ownerId == _instanceId && t_localCache != null)
{ {
var index = t_cacheIndex; return t_localCache;
if (index <= _cacheCount)
{
var cache = _caches[index];
if (cache != null)
{
return cache;
}
}
} }
return RegisterThreadCache(); return RegisterThreadCache();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly void* TryPopFromBucket(ThreadCache* cache, ushort cacheIndex, byte bucketIndex) private readonly void* TryPopFromBucket(ThreadCache* cache, byte bucketIndex)
{ {
var buckets = GetBuckets(cache); var buckets = GetBuckets(cache);
var bucket = &buckets[bucketIndex]; var bucket = &buckets[bucketIndex];
@@ -350,7 +367,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
bucket->freeHead = (nint)head->next; bucket->freeHead = (nint)head->next;
bucket->freeCount--; bucket->freeCount--;
AssignBlockHeader((BlockHeader*)head, head->ownerChunk, head->bucketIndex, cacheIndex); AssignBlockHeader((BlockHeader*)head, head->ownerChunk, head->bucketIndex, cache);
return head; return head;
} }
@@ -368,15 +385,15 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AssignBlockHeader(BlockHeader* header, MemoryChunk* ownerChunk, byte bucketIndex, ushort ownerCacheIndex) private static void AssignBlockHeader(BlockHeader* header, MemoryChunk* ownerChunk, byte bucketIndex, ThreadCache* ownerCache)
{ {
header->ownerChunk = ownerChunk; header->ownerChunk = ownerChunk;
header->bucketIndex = bucketIndex; header->bucketIndex = bucketIndex;
header->magicNumber = _MAGIC_NUMBER; header->magicNumber = _MAGIC_NUMBER;
header->ownerCacheIndex = ownerCacheIndex; header->ownerCache = ownerCache;
} }
private bool TryCreateBlocksForBucket(ThreadCache* cache, ushort cacheIndex, byte bucketIndex) private bool TryCreateBlocksForBucket(ThreadCache* cache, byte bucketIndex)
{ {
var buckets = GetBuckets(cache); var buckets = GetBuckets(cache);
var bucket = &buckets[bucketIndex]; var bucket = &buckets[bucketIndex];
@@ -509,31 +526,21 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
var totalSize = alignedSize + (nuint)sizeof(BlockHeader); var totalSize = alignedSize + (nuint)sizeof(BlockHeader);
var bucketIndex = FindBucket(totalSize); var bucketIndex = FindBucket(totalSize);
var cache = GetCurrentCache(); var cache = GetCurrentCache();
var cacheIndex = t_cacheIndex;
var requiresOverflowLock = cacheIndex == _OVERFLOW_CACHE_INDEX;
if (requiresOverflowLock)
{
while (Interlocked.CompareExchange(ref _overflowLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
}
try try
{ {
void* ptr = null; void* ptr = null;
if (bucketIndex != byte.MaxValue) if (bucketIndex != byte.MaxValue)
{ {
ptr = TryPopFromBucket(cache, cacheIndex, bucketIndex); ptr = TryPopFromBucket(cache, bucketIndex);
if (ptr == null) if (ptr == null)
{ {
DrainRemoteFrees(cache); DrainRemoteFrees(cache);
ptr = TryPopFromBucket(cache, cacheIndex, bucketIndex); ptr = TryPopFromBucket(cache, bucketIndex);
if (ptr == null && TryCreateBlocksForBucket(cache, cacheIndex, bucketIndex)) if (ptr == null && TryCreateBlocksForBucket(cache, bucketIndex))
{ {
ptr = TryPopFromBucket(cache, cacheIndex, bucketIndex); ptr = TryPopFromBucket(cache, bucketIndex);
} }
} }
} }
@@ -544,7 +551,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
if (ptr != null) if (ptr != null)
{ {
// Pass null for ownerChunk so 'Free' knows this is a standalone allocation // Pass null for ownerChunk so 'Free' knows this is a standalone allocation
AssignBlockHeader((BlockHeader*)ptr, null, bucketIndex, cacheIndex); AssignBlockHeader((BlockHeader*)ptr, null, bucketIndex, cache);
} }
} }
@@ -554,7 +561,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
} }
var header = (BlockHeader*)ptr; var header = (BlockHeader*)ptr;
header->ownerCacheIndex = cacheIndex; header->ownerCache = cache;
var userPtr = (byte*)ptr + sizeof(BlockHeader); var userPtr = (byte*)ptr + sizeof(BlockHeader);
if (allocationOption.HasFlag(AllocationOption.Clear)) if (allocationOption.HasFlag(AllocationOption.Clear))
@@ -566,10 +573,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
} }
finally finally
{ {
if (requiresOverflowLock)
{
Interlocked.Exchange(ref _overflowLock, 0);
}
} }
} }
@@ -598,7 +602,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
/// This is thread safe. /// This is thread safe.
/// </remarks> /// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Free(void* ptr) public readonly void Free(void* ptr)
{ {
if (_disposed != 0 || ptr == null) if (_disposed != 0 || ptr == null)
{ {
@@ -618,7 +622,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
return; return;
} }
var ownerCacheIndex = header->ownerCacheIndex; var targetCache = header->ownerCache;
var bucketIndex = header->bucketIndex; var bucketIndex = header->bucketIndex;
if (bucketIndex == byte.MaxValue) if (bucketIndex == byte.MaxValue)
@@ -630,38 +634,11 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
return; return;
} }
var sameThread = t_ownerId == _instanceId && t_cacheIndex == ownerCacheIndex; var sameThread = t_ownerId == _instanceId && t_localCache == targetCache;
var targetCache = ownerCacheIndex >= 0 && ownerCacheIndex <= _cacheCount ? _caches[ownerCacheIndex] : null;
if (targetCache == null)
{
targetCache = GetOverflowCache();
ownerCacheIndex = _OVERFLOW_CACHE_INDEX;
sameThread = t_ownerId == _instanceId && t_cacheIndex == ownerCacheIndex;
}
if (sameThread) if (sameThread)
{ {
if (ownerCacheIndex == _OVERFLOW_CACHE_INDEX) PushToBucket(targetCache, bucketIndex, blockStartPtr, chunk);
{
while (Interlocked.CompareExchange(ref _overflowLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
PushToBucket(targetCache, bucketIndex, blockStartPtr, chunk);
}
finally
{
Interlocked.Exchange(ref _overflowLock, 0);
}
}
else
{
PushToBucket(targetCache, bucketIndex, blockStartPtr, chunk);
}
return; return;
} }
@@ -684,27 +661,19 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
return; return;
} }
if (_caches != null)
{
for (var i = 0; i <= _cacheCount; i++)
{
var cache = _caches[i];
if (cache != null)
{
DrainRemoteFrees(cache);
cache->active = 0;
}
}
}
if (_caches != null)
{
MemoryUtility.Free(_caches);
_caches = null;
}
if (_instanceId != null) if (_instanceId != null)
{ {
var state = (SharedState*)_instanceId;
Volatile.Write(ref state->isDisposed, 1);
var current = state->headCache;
while (current != null)
{
DrainRemoteFrees(current);
current->active = 0;
current = current->next;
}
MemoryUtility.Free(_instanceId); MemoryUtility.Free(_instanceId);
_instanceId = null; _instanceId = null;
} }

View File

@@ -24,6 +24,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
private readonly nuint _originalOffset; private readonly nuint _originalOffset;
public readonly AllocationHandle AllocationHandle => _handle; public readonly AllocationHandle AllocationHandle => _handle;
public readonly nuint OriginalOffset => _originalOffset;
internal Scope(VirtualStack* allocator, AllocationHandle handle) internal Scope(VirtualStack* allocator, AllocationHandle handle)
{ {

View File

@@ -57,7 +57,7 @@ public class TestAllocationManager
} }
[TestMethod] [TestMethod]
public unsafe void StackAllocationTest() public void StackAllocationTest()
{ {
var thread = new Thread(() => var thread = new Thread(() =>
{ {
@@ -66,6 +66,8 @@ public class TestAllocationManager
Assert.IsTrue(ptr1.IsCreated); Assert.IsTrue(ptr1.IsCreated);
Thread.Sleep(100); // Simulate some work
ptr1.Dispose(); ptr1.Dispose();
scope.Dispose(); scope.Dispose();
}); });
@@ -73,6 +75,8 @@ public class TestAllocationManager
thread.Start(); thread.Start();
var scope = AllocationManager.CreateStackScope(); var scope = AllocationManager.CreateStackScope();
Assert.AreEqual(0u, scope.OriginalOffset);
var ptr2 = new MemoryBlock(1024, 8, scope.AllocationHandle); var ptr2 = new MemoryBlock(1024, 8, scope.AllocationHandle);
Assert.IsTrue(ptr2.IsCreated); Assert.IsTrue(ptr2.IsCreated);

View File

@@ -40,7 +40,7 @@ public unsafe class TestFreeList
{ {
const int threadCount = 8; const int threadCount = 8;
const int iterations = 1000; const int iterations = 1000;
using var freeList = new FreeList(8, 64 * 1024, threadCount); using var freeList = new FreeList(8, 64 * 1024);
var threads = new Thread[threadCount]; var threads = new Thread[threadCount];
for (var i = 0; i < threadCount; i++) for (var i = 0; i < threadCount; i++)
@@ -68,7 +68,7 @@ public unsafe class TestFreeList
const int producerCount = 4; const int producerCount = 4;
const int consumerCount = 4; const int consumerCount = 4;
const int iterations = 5000; const int iterations = 5000;
using var freeList = new FreeList(8, 64 * 1024, producerCount + consumerCount); using var freeList = new FreeList(8, 64 * 1024);
var queue = new System.Collections.Concurrent.ConcurrentQueue<IntPtr>(); var queue = new System.Collections.Concurrent.ConcurrentQueue<IntPtr>();
var producers = new Thread[producerCount]; var producers = new Thread[producerCount];
@@ -124,7 +124,7 @@ public unsafe class TestFreeList
{ {
// Set maxConcurrencyLevel to 1, but use more threads // Set maxConcurrencyLevel to 1, but use more threads
const int threadCount = 5; const int threadCount = 5;
using var freeList = new FreeList(8, 1024, 1); using var freeList = new FreeList(8, 1024);
var threads = new Thread[threadCount]; var threads = new Thread[threadCount];
for (var i = 0; i < threadCount; i++) for (var i = 0; i < threadCount; i++)