old data
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="BufferStack.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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 System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
[CollectionBuilder(typeof(BufferStackBuilder), nameof(BufferStackBuilder.Create))]
|
||||
public unsafe class BufferStack<T> : IDisposable where T: unmanaged
|
||||
{
|
||||
|
||||
private byte* _buffer;
|
||||
private ulong _count;
|
||||
|
||||
public BufferStack(int capacity)
|
||||
{
|
||||
if (capacity <= 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
}
|
||||
|
||||
AllocateBuffer(capacity);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (int)_count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Clear()
|
||||
{
|
||||
((Header*)_buffer)->Top = 0;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose()
|
||||
{
|
||||
Marshal.FreeHGlobal((nint)_buffer);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T Pop()
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
if (header->Top == 0) {
|
||||
throw new InvalidOperationException("Stack underflow");
|
||||
}
|
||||
|
||||
header->Top--;
|
||||
_count--;
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
return dataStart[header->Top];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Push(T item)
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
if (header->Top >= header->Capacity) {
|
||||
Expand();
|
||||
}
|
||||
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
dataStart[header->Top] = item;
|
||||
header->Top++;
|
||||
_count++;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryPop(out T value)
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
if (header->Top == 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
header->Top--;
|
||||
_count--;
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
value = dataStart[header->Top];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal method for efficient bulk initialization
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void InitializeFromSpan(ReadOnlySpan<T> span)
|
||||
{
|
||||
if (span.IsEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
Header* header = (Header*)_buffer;
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
|
||||
fixed (T* sourcePtr = span) {
|
||||
Buffer.MemoryCopy(sourcePtr, dataStart,
|
||||
header->Capacity * (nuint)sizeof(T),
|
||||
(nuint)span.Length * (nuint)sizeof(T));
|
||||
}
|
||||
|
||||
header->Top = (nuint)span.Length;
|
||||
_count = (ulong)span.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AllocateBuffer(int capacity)
|
||||
{
|
||||
nuint size = (nuint)sizeof(Header) + ((nuint)capacity * (nuint)sizeof(T));
|
||||
_buffer = (byte*)NativeMemory.Alloc(size);
|
||||
Header* header = (Header*)_buffer;
|
||||
header->Top = 0;
|
||||
header->Capacity = (nuint)capacity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Expand()
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
nuint newCap = header->Capacity * 2;
|
||||
nuint newSize = (nuint)sizeof(Header) + newCap * (nuint)sizeof(T);
|
||||
_buffer = (byte*)NativeMemory.Realloc(_buffer, newSize);
|
||||
header = (Header*)_buffer;
|
||||
header->Capacity = newCap;
|
||||
}
|
||||
|
||||
private struct Header
|
||||
{
|
||||
|
||||
public nuint Capacity;
|
||||
public nuint Top;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="ConcurrentBufferStack.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Threading;
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public static class BufferStackBuilder
|
||||
{
|
||||
|
||||
// For BufferStack collection builder
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static BufferStack<T> Create<T>(ReadOnlySpan<T> span) where T: unmanaged
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return new BufferStack<T>(4); // Default small capacity
|
||||
}
|
||||
|
||||
BufferStack<T> stack = new BufferStack<T>(span.Length);
|
||||
|
||||
|
||||
// Use internal method for efficient bulk initialization
|
||||
stack.InitializeFromSpan(span);
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
// For ConcurrentBufferStack collection builder
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ConcurrentBufferStack<T> CreateConcurrent<T>(ReadOnlySpan<T> span) where T: unmanaged
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return new ConcurrentBufferStack<T>(4); // Default small capacity
|
||||
}
|
||||
|
||||
ConcurrentBufferStack<T> stack = new ConcurrentBufferStack<T>(span.Length);
|
||||
|
||||
|
||||
// Use internal method for efficient bulk initialization
|
||||
stack.InitializeFromSpan(span);
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[CollectionBuilder(builderType: typeof(BufferStackBuilder), methodName: nameof(BufferStackBuilder.CreateConcurrent))]
|
||||
public unsafe class ConcurrentBufferStack<T> : IDisposable where T: unmanaged
|
||||
{
|
||||
|
||||
private byte* _buffer;
|
||||
private readonly BufferExpansionLock _lock = new();
|
||||
private ulong _count;
|
||||
|
||||
public ConcurrentBufferStack(int capacity)
|
||||
{
|
||||
if (capacity <= 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
}
|
||||
|
||||
AllocateBuffer(capacity);
|
||||
}
|
||||
|
||||
public ulong Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Volatile.Read(ref _count);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
header->Top = 0;
|
||||
Volatile.Write(ref _count, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_lock.Dispose();
|
||||
Marshal.FreeHGlobal((nint)_buffer);
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
while (true)
|
||||
{
|
||||
nuint oldTop = Volatile.Read(ref header->Top);
|
||||
if (oldTop <= 0) {
|
||||
throw new InvalidOperationException("Stack underflow");
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop)
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
try
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
_ = Interlocked.Decrement(ref _count);
|
||||
return dataStart[oldTop - 1];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
}
|
||||
Thread.Sleep(0); // Yield to avoid busy waiting
|
||||
}
|
||||
}
|
||||
|
||||
public void Push(T item)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_lock.EnterMetaDataRead();
|
||||
try
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
nuint oldTop = Volatile.Read(ref header->Top);
|
||||
nuint cap = Volatile.Read(ref header->Capacity);
|
||||
if (oldTop >= cap)
|
||||
{
|
||||
_lock.ExitMetaDataRead();
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
header = (Header*)_buffer;
|
||||
if (header->Top >= header->Capacity)
|
||||
{
|
||||
nuint newCap = header->Capacity * 2;
|
||||
nuint newSize = (nuint)sizeof(Header) + (newCap * (nuint)sizeof(T));
|
||||
|
||||
_lock.EnterBufferCopy();
|
||||
try
|
||||
{
|
||||
_buffer = (byte*)NativeMemory.Realloc(_buffer, newSize);
|
||||
((Header*)_buffer)->Capacity = newCap;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferCopy();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop + 1, oldTop) == oldTop)
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
try
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
dataStart[oldTop] = item;
|
||||
_ = Interlocked.Increment(ref _count);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsThreadReadingMetaData) {
|
||||
_lock.ExitMetaDataRead();
|
||||
}
|
||||
}
|
||||
Thread.Sleep(0); // Yield to avoid busy waiting
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPop(out T value)
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
while (true)
|
||||
{
|
||||
nuint oldTop = Volatile.Read(ref header->Top);
|
||||
if (oldTop <= 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop)
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
try
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
value = dataStart[oldTop - 1];
|
||||
_ = Interlocked.Decrement(ref _count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Thread.Sleep(0); // Yield to avoid busy waiting
|
||||
}
|
||||
}
|
||||
|
||||
// Internal method for efficient bulk initialization
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void InitializeFromSpan(ReadOnlySpan<T> span)
|
||||
{
|
||||
if (span.IsEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
Header* header = (Header*)_buffer;
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
|
||||
fixed (T* sourcePtr = span) {
|
||||
Buffer.MemoryCopy(sourcePtr, dataStart,
|
||||
header->Capacity * (nuint)sizeof(T),
|
||||
(nuint)span.Length * (nuint)sizeof(T));
|
||||
}
|
||||
|
||||
header->Top = (nuint)span.Length;
|
||||
Volatile.Write(ref _count, (ulong)span.Length);
|
||||
}
|
||||
|
||||
private void AllocateBuffer(int capacity)
|
||||
{
|
||||
nuint size = (nuint)sizeof(Header) + ((nuint)capacity * (nuint)sizeof(T));
|
||||
_buffer = (byte*)NativeMemory.Alloc(size);
|
||||
Header* header = (Header*)_buffer;
|
||||
header->Top = 0;
|
||||
header->Capacity = (nuint)capacity;
|
||||
}
|
||||
|
||||
private struct Header
|
||||
{
|
||||
|
||||
public nuint Capacity; // max capacity of the stack (number of elements)
|
||||
public nuint Top; // next available index in the stack
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="ConcurrentStackBuffer.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Threading;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public static class ConcurrentStackBufferBuilder
|
||||
{
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ConcurrentStackBuffer<T> BuildStack<T>(ReadOnlySpan<T> span)
|
||||
where T: unmanaged
|
||||
{
|
||||
return new ConcurrentStackBuffer<T>(span);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[CollectionBuilder(
|
||||
builderType: typeof(ConcurrentStackBufferBuilder),
|
||||
methodName: nameof(ConcurrentStackBufferBuilder.BuildStack))]
|
||||
public unsafe class ConcurrentStackBuffer<T> : IDisposable where T: unmanaged
|
||||
{
|
||||
|
||||
private byte* _buffer;
|
||||
private readonly BufferExpansionLock _lock = new();
|
||||
private ulong _count;
|
||||
|
||||
internal ConcurrentStackBuffer(ReadOnlySpan<T> span)
|
||||
{
|
||||
AllocateBuffer(span.Length);
|
||||
Span<T> targe = new(_buffer + sizeof(Header), span.Length);
|
||||
span.CopyTo(targe);
|
||||
}
|
||||
|
||||
public ConcurrentStackBuffer(int capacity)
|
||||
{
|
||||
if (capacity <= 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
}
|
||||
|
||||
AllocateBuffer(capacity);
|
||||
}
|
||||
|
||||
public ulong Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Volatile.Read(ref _count);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
header->Top = 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_lock.Dispose();
|
||||
Marshal.FreeHGlobal((nint)_buffer);
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
while (true)
|
||||
{
|
||||
nuint oldTop = Volatile.Read(ref header->Top);
|
||||
if (oldTop <= 0) {
|
||||
throw new InvalidOperationException("Stack underflow");
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop)
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
try
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
_ = Interlocked.Decrement(ref _count);
|
||||
return dataStart[oldTop - 1];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
}
|
||||
Thread.Sleep(0); // Yield to avoid busy waiting
|
||||
}
|
||||
}
|
||||
|
||||
public void Push(T item)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_lock.EnterMetaDataRead();
|
||||
try
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
nuint oldTop = Volatile.Read(ref header->Top);
|
||||
nuint cap = Volatile.Read(ref header->Capacity);
|
||||
if (oldTop >= cap)
|
||||
{
|
||||
_lock.ExitMetaDataRead();
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
header = (Header*)_buffer;
|
||||
if (header->Top >= header->Capacity)
|
||||
{
|
||||
nuint newCap = header->Capacity * 2;
|
||||
nuint newSize = (nuint)sizeof(Header) + (newCap * (nuint)sizeof(T));
|
||||
|
||||
_lock.EnterBufferCopy();
|
||||
try
|
||||
{
|
||||
_buffer = (byte*)NativeMemory.Realloc(_buffer, newSize);
|
||||
((Header*)_buffer)->Capacity = newCap;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferCopy();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop + 1, oldTop) == oldTop)
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
try
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
dataStart[oldTop] = item;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsThreadReadingMetaData) {
|
||||
_lock.ExitMetaDataRead();
|
||||
}
|
||||
}
|
||||
Thread.Sleep(0); // Yield to avoid busy waiting
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPop(out T value)
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
while (true)
|
||||
{
|
||||
nuint oldTop = Volatile.Read(ref header->Top);
|
||||
if (oldTop <= 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop)
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
try
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
value = dataStart[oldTop - 1];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Thread.Sleep(0); // Yield to avoid busy waiting
|
||||
}
|
||||
}
|
||||
|
||||
private void AllocateBuffer(int capacity)
|
||||
{
|
||||
nuint size = (nuint)sizeof(Header) + ((nuint)capacity * (nuint)sizeof(T));
|
||||
_buffer = (byte*)NativeMemory.Alloc(size);
|
||||
Header* header = (Header*)_buffer;
|
||||
header->Top = 0;
|
||||
header->Capacity = (nuint)capacity;
|
||||
}
|
||||
|
||||
private struct Header
|
||||
{
|
||||
|
||||
public nuint Capacity; // max capacity of the stack (number of elements)
|
||||
public nuint Top; // next available index in the stack
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,17 @@
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using StgSharp.Entities;
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Allocator = StgSharp.HighPerformance.Memory.HybridLayerSegregatedFitAllocator;
|
||||
|
||||
@@ -43,6 +48,7 @@ namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
|
||||
internal readonly byte* BufferPointer;
|
||||
|
||||
internal readonly Allocator.Entry* EntryHandle;
|
||||
public readonly uint AllocSize;
|
||||
|
||||
|
||||
@@ -57,10 +57,13 @@ namespace StgSharp.HighPerformance.Memory
|
||||
int segmentIndex = DetermineSegmentIndex(size, level);
|
||||
|
||||
int i = level, s = segmentIndex;
|
||||
if (TryPopLevelForAllocation(i, s, out Entry* handle)) {
|
||||
if (TryPopLevelForAllocation(i, s, out Entry* handle))
|
||||
{
|
||||
SetEntryState(handle, EntryState.Allocated);
|
||||
return new hlsfHandle(handle, size);
|
||||
}
|
||||
|
||||
fallback:
|
||||
i = level;
|
||||
s = segmentIndex;
|
||||
while (!TryPopLevelForAllocation(i, s, out handle))
|
||||
@@ -76,115 +79,146 @@ namespace StgSharp.HighPerformance.Memory
|
||||
throw new OverflowException("Out of memory: Unable to allocate new 16MB block or find suitable bucket for allocation.");
|
||||
}
|
||||
|
||||
break;
|
||||
Thread.Sleep(0);
|
||||
goto fallback;
|
||||
}
|
||||
}
|
||||
|
||||
if (handle != null) {
|
||||
SliceMemory(handle, (segmentIndex + 1) * levelSizeArray[level]);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (handle != null)
|
||||
{
|
||||
AcquireEntryNextLock(handle);
|
||||
SliceMemory(handle, (segmentIndex + 1) * levelSizeArray[level]);
|
||||
SetEntryState(handle, EntryState.Allocated);
|
||||
}
|
||||
|
||||
return new hlsfHandle(handle, size);
|
||||
return new hlsfHandle(handle, size);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseEntryNextLock(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SliceMemory(Entry* e, int offset)
|
||||
{
|
||||
Entry* current = e;
|
||||
ulong n = e->NextNear;
|
||||
Entry* next = (Entry*)n;
|
||||
Entry* next = e;
|
||||
|
||||
ulong originOffset = e->Position - (ulong)m_Buffer;
|
||||
ulong finalOffset = originOffset + (ulong)offset;
|
||||
ulong end = e->Position + e->Size - (ulong)m_Buffer;
|
||||
int remainSize = (int)(e->Size - offset);
|
||||
|
||||
if ((originOffset & 63) != 0) {
|
||||
throw new InvalidOperationException("Entry position offset is not aligned to 64 bytes.");
|
||||
}
|
||||
AcquireEntryNextLock(e);
|
||||
AcquireEntryPrevLock(next);
|
||||
|
||||
int minLevel = Math.Max((BitOperations.TrailingZeroCount(finalOffset) - 6) / 2, 0);
|
||||
int i = minLevel >= levelSizeArray.Length ? levelSizeArray.Length - 1 : minLevel;
|
||||
|
||||
for (; i < levelSizeArray.Length; i++)
|
||||
try
|
||||
{
|
||||
int levelSize = levelSizeArray[i];
|
||||
ulong board = (finalOffset + (ulong)levelSize - 1) & ~((ulong)levelSize - 1);
|
||||
int blockCount = (int)((ulong.Min(board, end) - board) / (ulong)levelSize);
|
||||
if (blockCount == 0)
|
||||
if ((originOffset & 63) != 0) {
|
||||
throw new InvalidOperationException("Entry position offset is not aligned to 64 bytes.");
|
||||
}
|
||||
int minLevel = Math.Max((BitOperations.TrailingZeroCount(originOffset) - 6) / 2, 0);
|
||||
int i = minLevel >= levelSizeArray.Length ? levelSizeArray.Length - 1 : minLevel;
|
||||
for (; i < levelSizeArray.Length; i++)
|
||||
{
|
||||
break;
|
||||
}
|
||||
int s = blockCount * levelSize;
|
||||
int levelSize = levelSizeArray[i];
|
||||
int blockCount = remainSize / levelSize % 4;
|
||||
if (blockCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
int s = blockCount * levelSize;
|
||||
|
||||
// create and init new entry
|
||||
Entry* newEntry = (Entry*)_entries.Allocate();
|
||||
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
|
||||
newEntry->Size = (uint)s;
|
||||
BucketNode* bucket = (BucketNode*)newEntry->Position;
|
||||
bucket->EntryRef = newEntry;
|
||||
// create and init new entry
|
||||
Entry* newEntry = (Entry*)_entries.Allocate();
|
||||
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
|
||||
newEntry->Size = (uint)s;
|
||||
newEntry->LockBuffer = 0;
|
||||
BucketNode* bucket = (BucketNode*)newEntry->Position;
|
||||
bucket->EntryRef = newEntry;
|
||||
bucket->LockBuffer = 0;
|
||||
newEntry->State = EntryState.ThreadOccupied;
|
||||
|
||||
// link new entry
|
||||
newEntry->PreviousNear = (ulong)current;
|
||||
newEntry->NextNear = n;
|
||||
current->NextNear = (ulong)newEntry;
|
||||
if (next != null) {
|
||||
// link new entry
|
||||
newEntry->PreviousNear = (ulong)current;
|
||||
newEntry->NextNear = n;
|
||||
current->NextNear = (ulong)newEntry;
|
||||
next->PreviousNear = (ulong)newEntry;
|
||||
|
||||
newEntry->Level = i;
|
||||
PushLevel(i, blockCount - 1, bucket);
|
||||
|
||||
// set new entry
|
||||
Entry* old = current;
|
||||
current = newEntry;
|
||||
|
||||
// update lock and data
|
||||
finalOffset += (ulong)s;
|
||||
remainSize -= s;
|
||||
ReleaseEntryNextLock(old);
|
||||
AcquireEntryNextLock(current);
|
||||
}
|
||||
|
||||
newEntry->Level = i;
|
||||
PushLevel(i, blockCount - 1, bucket);
|
||||
|
||||
// set new entry
|
||||
current = newEntry;
|
||||
|
||||
// update data
|
||||
finalOffset += (ulong)s;
|
||||
remainSize -= s;
|
||||
}
|
||||
|
||||
if (remainSize <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i -= 1; i >= 0; i--)
|
||||
{
|
||||
int levelSize = levelSizeArray[i];
|
||||
ulong board = (finalOffset + (ulong)levelSize - 1) & ~((ulong)levelSize - 1);
|
||||
int blockCount = (int)((ulong.Min(board, end) - finalOffset) / (ulong)levelSize);
|
||||
if (blockCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int s = blockCount * levelSize;
|
||||
|
||||
// create and init new entry
|
||||
Entry* newEntry = (Entry*)_entries.Allocate();
|
||||
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
|
||||
newEntry->Size = (uint)s;
|
||||
BucketNode* bucket = (BucketNode*)newEntry->Position;
|
||||
bucket->EntryRef = newEntry;
|
||||
|
||||
// link new entry
|
||||
newEntry->PreviousNear = (ulong)current;
|
||||
newEntry->NextNear = n;
|
||||
current->NextNear = (ulong)newEntry;
|
||||
if (next != null) {
|
||||
next->PreviousNear = (ulong)newEntry;
|
||||
}
|
||||
|
||||
newEntry->Level = i;
|
||||
PushLevel(i, blockCount - 1, bucket);
|
||||
|
||||
// set new entry
|
||||
current = newEntry;
|
||||
|
||||
// update data
|
||||
finalOffset += (ulong)s;
|
||||
remainSize -= s;
|
||||
if (remainSize <= 0)
|
||||
{
|
||||
break;
|
||||
ReleaseEntryNextLock(current);
|
||||
ReleaseEntryPrevLock(next);
|
||||
return;
|
||||
}
|
||||
for (; i >= 0; i--)
|
||||
{
|
||||
int levelSize = levelSizeArray[i];
|
||||
int blockCount = remainSize / levelSize % 4;
|
||||
if (blockCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int s = blockCount * levelSize;
|
||||
|
||||
// create and init new entry
|
||||
Entry* newEntry = (Entry*)_entries.Allocate();
|
||||
newEntry->LockBuffer = 0;
|
||||
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
|
||||
newEntry->Size = (uint)s;
|
||||
BucketNode* bucket = (BucketNode*)newEntry->Position;
|
||||
bucket->EntryRef = newEntry;
|
||||
bucket->LockBuffer = 0;
|
||||
newEntry->State = EntryState.ThreadOccupied;
|
||||
|
||||
// link new entry
|
||||
newEntry->PreviousNear = (ulong)current;
|
||||
current->NextNear = (ulong)newEntry;
|
||||
newEntry->NextNear = n;
|
||||
next->PreviousNear = (ulong)newEntry;
|
||||
|
||||
newEntry->Level = i;
|
||||
PushLevel(i, blockCount - 1, bucket);
|
||||
|
||||
// set new entry
|
||||
Entry* old = current;
|
||||
current = newEntry;
|
||||
|
||||
// update lock and data
|
||||
finalOffset += (ulong)s;
|
||||
remainSize -= s;
|
||||
ReleaseEntryNextLock(old);
|
||||
AcquireEntryNextLock(current);
|
||||
if (remainSize <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release locks in reverse order
|
||||
ReleaseEntryNextLock(current);
|
||||
ReleaseEntryPrevLock(next);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,13 +233,22 @@ namespace StgSharp.HighPerformance.Memory
|
||||
/// </returns>
|
||||
private bool TryAllocateNew16MBBlock(out Entry* handle)
|
||||
{
|
||||
nuint pos = _spareMemory->Position;
|
||||
nuint pos = Volatile.Read(ref _spareMemory->Position);
|
||||
nuint blockSize = (nuint)levelSizeArray[^1] * 2; // Use last level size (16MB)
|
||||
nuint newPos = pos + blockSize;
|
||||
|
||||
ulong p = _spareMemory->PreviousNear;
|
||||
Entry* previous = (Entry*)p;
|
||||
|
||||
ulong p;
|
||||
Entry* previous;
|
||||
do
|
||||
{
|
||||
p = Volatile.Read(ref _spareMemory->PreviousNear);
|
||||
previous = (Entry*)p;
|
||||
if (previous != null) {
|
||||
AcquireEntryNextLock(previous);
|
||||
}
|
||||
AcquireEntryPrevLock(_spareMemory);
|
||||
break;
|
||||
} while (true);
|
||||
if (_spareMemory->Size <= 0)
|
||||
{
|
||||
handle = null;
|
||||
@@ -220,25 +263,42 @@ namespace StgSharp.HighPerformance.Memory
|
||||
}
|
||||
|
||||
// Try to allocate new Entry from slab allocator
|
||||
handle = (Entry*)_entries.Allocate();
|
||||
handle->Position = pos;
|
||||
handle->Size = (uint)blockSize;
|
||||
handle->Level = GetLevelFromSize((uint)blockSize);
|
||||
try
|
||||
{
|
||||
handle = (Entry*)_entries.Allocate();
|
||||
handle->Position = pos;
|
||||
handle->Size = (uint)blockSize;
|
||||
SetEntryState(handle, EntryState.ThreadOccupied);
|
||||
handle->Level = GetLevelFromSize((uint)blockSize);
|
||||
|
||||
// Update spare memory position
|
||||
_spareMemory->Position = newPos;
|
||||
BucketNode* b = (BucketNode*)handle->Position;
|
||||
b->EntryRef = handle;
|
||||
PushLevel(handle->Level, DetermineSegmentIndex((uint)blockSize, handle->Level), b);
|
||||
handle->PrevLock = 0;
|
||||
handle->NextLock = 0;
|
||||
|
||||
// update position linked list
|
||||
handle->NextNear = (ulong)_spareMemory;
|
||||
handle->PreviousNear = p;
|
||||
_spareMemory->PreviousNear = (ulong)handle;
|
||||
if (previous != null) {
|
||||
previous->NextNear = (ulong)handle;
|
||||
// Update spare memory position
|
||||
_spareMemory->Position = newPos;
|
||||
BucketNode* b = (BucketNode*)handle->Position;
|
||||
b->EntryRef = handle;
|
||||
PushLevel(handle->Level, DetermineSegmentIndex((uint)blockSize, handle->Level), b);
|
||||
|
||||
// update position linked list
|
||||
handle->NextNear = (ulong)_spareMemory;
|
||||
handle->PreviousNear = p;
|
||||
_spareMemory->PreviousNear = (ulong)handle;
|
||||
if (previous != null) {
|
||||
previous->NextNear = (ulong)handle;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
handle = null;
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseEntryNextLock(previous);
|
||||
ReleaseEntryPrevLock(_spareMemory);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -39,6 +39,12 @@ namespace StgSharp.HighPerformance.Memory
|
||||
public unsafe partial class HybridLayerSegregatedFitAllocator
|
||||
{
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireBucketNodeSpinLock(BucketNode* node)
|
||||
{
|
||||
AcquireBucketNodeBothLocks(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize bucket system (called in constructor)
|
||||
/// </summary>
|
||||
@@ -47,16 +53,6 @@ namespace StgSharp.HighPerformance.Memory
|
||||
_bucketHeads = new ulong[levelSizeArray.Length * 3];
|
||||
}
|
||||
|
||||
#region BucketNode Operations
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsNullOrEmpty(BucketNode* node)
|
||||
{
|
||||
return node == null || node->Magic != BucketNode.MAGIC_NUMBER;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void PushLevel(int levelIndex, int sizeIndex, BucketNode* node)
|
||||
{
|
||||
if (node == null || node->EntryRef == null) {
|
||||
@@ -64,112 +60,269 @@ namespace StgSharp.HighPerformance.Memory
|
||||
}
|
||||
|
||||
int index = (3 * levelIndex) + sizeIndex;
|
||||
if (_bucketHeads[index] == EmptyHandle)
|
||||
if (Interlocked.CompareExchange(ref _bucketHeads[index], (nuint)node, EmptyHandle) == EmptyHandle)
|
||||
{
|
||||
_bucketHeads[index] = (nuint)node;
|
||||
node->IsInBucket = true;
|
||||
node->Magic = BucketNode.MAGIC_NUMBER;
|
||||
node->NextLevel = EmptyHandle;
|
||||
node->PreviousLevel = EmptyHandle;
|
||||
return;
|
||||
} else
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
AcquireBucketNodeNextLock(node);
|
||||
ulong head = Volatile.Read(ref _bucketHeads[index]);
|
||||
node->NextLevel = head;
|
||||
BucketNode* headNode = (BucketNode*)head;
|
||||
|
||||
try
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _bucketHeads[index], (nuint)node, head) == head)
|
||||
{
|
||||
if (headNode != null && !TryAcquireBucketNodePrevLock(headNode))
|
||||
{
|
||||
// reset state and retry
|
||||
_ = Interlocked.CompareExchange(ref _bucketHeads[index], head, (nuint)node);
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
node->IsInBucket = true;
|
||||
node->Magic = BucketNode.MAGIC_NUMBER;
|
||||
if (headNode != null) {
|
||||
headNode->PreviousLevel = (ulong)node;
|
||||
}
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (headNode != null) {
|
||||
ReleaseBucketNodePrevLock(headNode);
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseBucketNodeNextLock(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add to head of linked list
|
||||
ulong head = _bucketHeads[index];
|
||||
BucketNode* headNode = (BucketNode*)head;
|
||||
|
||||
node->NextLevel = head;
|
||||
node->PreviousLevel = EmptyHandle;
|
||||
node->IsInBucket = true;
|
||||
node->Magic = BucketNode.MAGIC_NUMBER;
|
||||
|
||||
if (headNode != null) {
|
||||
headNode->PreviousLevel = (ulong)node;
|
||||
/// <summary>
|
||||
/// 根据锁类型释放BucketNode锁
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseBucketNodeLockByType(BucketNode* node, byte lockType)
|
||||
{
|
||||
switch (lockType)
|
||||
{
|
||||
case 0:
|
||||
ReleaseBucketNodePrevLock(node);
|
||||
return; // PrevLock
|
||||
case 1:
|
||||
ReleaseBucketNodeBothLocks(node);
|
||||
return; // BothLocks
|
||||
case 2:
|
||||
ReleaseBucketNodeNextLock(node);
|
||||
return; // NextLock
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_bucketHeads[index] = (nuint)node;
|
||||
/// <summary>
|
||||
/// 按地址顺序释放BucketNode锁
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseOrderedBucketLocks(BucketNode* previous, BucketNode* node, BucketNode* next)
|
||||
{
|
||||
if (previous != null) {
|
||||
ReleaseBucketNodeNextLock(previous);
|
||||
}
|
||||
if (node != null) {
|
||||
ReleaseBucketNodeBothLocks(node);
|
||||
}
|
||||
if (next != null) {
|
||||
ReleaseBucketNodePrevLock(next);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveFromBucket(int levelIndex, int sizeIndex, BucketNode* node)
|
||||
{
|
||||
int index = (3 * levelIndex) + sizeIndex;
|
||||
|
||||
if ((BucketNode*)_bucketHeads[index] == node)
|
||||
while (true)
|
||||
{
|
||||
_bucketHeads[index] = EmptyHandle;
|
||||
return;
|
||||
}
|
||||
if ((BucketNode*)Volatile.Read(ref _bucketHeads[index]) == node)
|
||||
{
|
||||
if (TryPopLevel(levelIndex, sizeIndex, out _)) {
|
||||
return;
|
||||
}
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong p = node->PreviousLevel;
|
||||
ulong n = node->NextLevel;
|
||||
BucketNode* previous = (BucketNode*)p;
|
||||
BucketNode* next = (BucketNode*)n;
|
||||
ulong p = Volatile.Read(ref node->PreviousLevel);
|
||||
ulong n = Volatile.Read(ref node->NextLevel);
|
||||
BucketNode* previous = (BucketNode*)p;
|
||||
BucketNode* next = (BucketNode*)n;
|
||||
|
||||
if (previous != null) {
|
||||
previous->NextLevel = n;
|
||||
}
|
||||
if (next != null) {
|
||||
next->PreviousLevel = p;
|
||||
}
|
||||
if (!TryAcquireOrderedBucketLocks(previous, node, next))
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
node->NextLevel = EmptyHandle;
|
||||
node->PreviousLevel = EmptyHandle;
|
||||
node->IsInBucket = false;
|
||||
try
|
||||
{
|
||||
if (Volatile.Read(ref node->PreviousLevel) != p || Volatile.Read(ref node->NextLevel) != n)
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previous != null) {
|
||||
previous->NextLevel = n;
|
||||
}
|
||||
if (next != null) {
|
||||
next->PreviousLevel = p;
|
||||
}
|
||||
|
||||
node->NextLevel = EmptyHandle;
|
||||
node->PreviousLevel = EmptyHandle;
|
||||
node->IsInBucket = false;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseOrderedBucketLocks(previous, node, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireBucketNodeSpinLock(BucketNode* node)
|
||||
{
|
||||
return TryAcquireBucketNodeBothLocks(node);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireOrderedBucketLocks(BucketNode* previous, BucketNode* node, BucketNode* next)
|
||||
{
|
||||
if (previous != null && node != null && next != null)
|
||||
{
|
||||
// previous -> node -> next
|
||||
return TryAcquireBucketNodeNextLock(previous) &&
|
||||
TryAcquireBucketNodeBothLocks(node) &&
|
||||
TryAcquireBucketNodePrevLock(next);
|
||||
} else if (previous != null && node != null) {
|
||||
return TryAcquireBucketNodeNextLock(previous) && TryAcquireBucketNodeBothLocks(node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryPopLevel(int levelIndex, int sizeIndex, out BucketNode* node)
|
||||
{
|
||||
int index = (3 * levelIndex) + sizeIndex;
|
||||
ulong h = _bucketHeads[index];
|
||||
BucketNode* head = (BucketNode*)h;
|
||||
|
||||
if (head == null)
|
||||
while (true)
|
||||
{
|
||||
node = null;
|
||||
return false;
|
||||
ulong h = Volatile.Read(ref _bucketHeads[index]);
|
||||
BucketNode* head = (BucketNode*)h;
|
||||
|
||||
if (head == null)
|
||||
{
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryAcquireBucketNodeNextLock(head))
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong n = Volatile.Read(ref head->NextLevel);
|
||||
BucketNode* next = (BucketNode*)n;
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _bucketHeads[index], EmptyHandle, h) == h)
|
||||
{
|
||||
node = head;
|
||||
return true;
|
||||
}
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!TryAcquireBucketNodePrevLock(next))
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _bucketHeads[index], head->NextLevel, h) == h)
|
||||
{
|
||||
node = head;
|
||||
node->IsInBucket = false;
|
||||
node->NextLevel = EmptyHandle;
|
||||
if (next != null) {
|
||||
next->PreviousLevel = EmptyHandle;
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (next != null) {
|
||||
ReleaseBucketNodePrevLock(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseBucketNodeNextLock(head);
|
||||
}
|
||||
}
|
||||
|
||||
ulong n = head->NextLevel;
|
||||
BucketNode* next = (BucketNode*)n;
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
_bucketHeads[index] = EmptyHandle;
|
||||
node = head;
|
||||
node->IsInBucket = false;
|
||||
node->NextLevel = EmptyHandle;
|
||||
node->PreviousLevel = EmptyHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
_bucketHeads[index] = head->NextLevel;
|
||||
node = head;
|
||||
node->IsInBucket = false;
|
||||
node->NextLevel = EmptyHandle;
|
||||
|
||||
if (next != null) {
|
||||
next->PreviousLevel = EmptyHandle;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryRemoveFromBucket(int levelIndex, int sizeIndex, BucketNode* node)
|
||||
{
|
||||
int index = (3 * levelIndex) + sizeIndex;
|
||||
if ((BucketNode*)_bucketHeads[index] == node)
|
||||
if ((BucketNode*)Volatile.Read(ref _bucketHeads[index]) == node)
|
||||
{
|
||||
_bucketHeads[index] = EmptyHandle;
|
||||
return true;
|
||||
if (TryPopLevel(levelIndex, sizeIndex, out _)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong p = Volatile.Read(ref node->PreviousLevel);
|
||||
ulong n = Volatile.Read(ref node->NextLevel);
|
||||
BucketNode* previous = (BucketNode*)p;
|
||||
BucketNode* next = (BucketNode*)n;
|
||||
|
||||
if (!TryAcquireOrderedBucketLocks(previous, node, next)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ulong p = node->PreviousLevel;
|
||||
ulong n = node->NextLevel;
|
||||
BucketNode* previous = (BucketNode*)p;
|
||||
BucketNode* next = (BucketNode*)n;
|
||||
if (Volatile.Read(ref node->PreviousLevel) != p || Volatile.Read(ref node->NextLevel) != n) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (previous != null) {
|
||||
previous->NextLevel = n;
|
||||
@@ -183,9 +336,9 @@ namespace StgSharp.HighPerformance.Memory
|
||||
node->IsInBucket = false;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
finally
|
||||
{
|
||||
return false;
|
||||
ReleaseOrderedBucketLocks(previous, node, next);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +346,7 @@ namespace StgSharp.HighPerformance.Memory
|
||||
/// BucketNode structure - specialized node for bucket linked lists Stored in main
|
||||
/// allocation area (user-overwritable region)
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 32)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = 40)]
|
||||
internal struct BucketNode
|
||||
{
|
||||
|
||||
@@ -201,12 +354,149 @@ namespace StgSharp.HighPerformance.Memory
|
||||
|
||||
[FieldOffset(16)] internal Entry* EntryRef; // Pointer to corresponding Entry
|
||||
[FieldOffset(28)] internal bool IsInBucket; // Whether in bucket
|
||||
[FieldOffset(36)] internal int NextLock;
|
||||
[FieldOffset(32)] internal int PrevLock;
|
||||
[FieldOffset(24)] internal uint Magic; // Magic number verification
|
||||
[FieldOffset(32)] internal ulong LockBuffer;
|
||||
|
||||
[FieldOffset(0)] internal ulong NextLevel; // Bucket stack linked list: next pointer
|
||||
[FieldOffset(8)] internal ulong PreviousLevel; // Bucket stack linked list: previous pointer
|
||||
|
||||
}
|
||||
|
||||
#region BucketNode Operations
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsNullOrEmpty(BucketNode* node)
|
||||
{
|
||||
return node == null || node->Magic != BucketNode.MAGIC_NUMBER;
|
||||
}
|
||||
|
||||
// BucketNode 前驱锁操作(控制PreviousLevel字段)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireBucketNodePrevLock(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
return Interlocked.CompareExchange(ref node->PrevLock, thread, 0) == 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireBucketNodePrevLock(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref node->PrevLock) != thread)
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref node->PrevLock, thread, 0) != 0) {
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseBucketNodePrevLock(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref node->PrevLock) == thread) {
|
||||
Volatile.Write(ref node->PrevLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// BucketNode 后继锁操作(控制NextLevel字段)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireBucketNodeNextLock(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
return Interlocked.CompareExchange(ref node->NextLock, thread, 0) == 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireBucketNodeNextLock(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref node->NextLock) != thread)
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref node->NextLock, thread, 0) != 0) {
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseBucketNodeNextLock(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref node->NextLock) == thread) {
|
||||
Volatile.Write(ref node->NextLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireBucketNodeBothLocks(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryAcquireBucketNodePrevLock(node))
|
||||
{
|
||||
if (TryAcquireBucketNodeNextLock(node))
|
||||
{
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
ReleaseBucketNodePrevLock(node); // 回滚
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseBucketNodeBothLocks(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReleaseBucketNodePrevLock(node);
|
||||
ReleaseBucketNodeNextLock(node);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireBucketNodeBothLocks(BucketNode* node)
|
||||
{
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AcquireBucketNodePrevLock(node);
|
||||
AcquireBucketNodeNextLock(node);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,44 +41,6 @@ namespace StgSharp.HighPerformance.Memory
|
||||
|
||||
private object _lock = new object();
|
||||
|
||||
public void DisplayAllocatedEntryLayout(bool singleStep)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Entry* cur = _spareMemory;
|
||||
while (cur != null)
|
||||
{
|
||||
if (cur->State == EntryState.Allocated)
|
||||
{
|
||||
Console.WriteLine(EntryToString(cur));
|
||||
if (singleStep) {
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
cur = (Entry*)cur->PreviousNear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DisplayEmptyEntryLayout(bool singleStep)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Entry* cur = _spareMemory;
|
||||
while (cur != null)
|
||||
{
|
||||
if (cur->State == EntryState.Empty)
|
||||
{
|
||||
Console.WriteLine(EntryToString(cur));
|
||||
if (singleStep) {
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
cur = (Entry*)cur->PreviousNear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DisplayEntryLayout(bool singleStep)
|
||||
{
|
||||
lock (_lock)
|
||||
|
||||
@@ -28,6 +28,13 @@
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using hlsfHandle = StgSharp.HighPerformance.Memory.HybridLayerSegregatedFitAllocationHandle;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
@@ -47,32 +54,36 @@ namespace StgSharp.HighPerformance.Memory
|
||||
if (e == null) {
|
||||
return;
|
||||
}
|
||||
AcquireEntrySpinLock(e);
|
||||
try
|
||||
{
|
||||
if (GetEntryState(e) != EntryState.Allocated) {
|
||||
return;
|
||||
}
|
||||
SetEntryState(e, EntryState.ThreadOccupied);
|
||||
|
||||
if (e->State != EntryState.Allocated) {
|
||||
return;
|
||||
// Step 4: Try merge with adjacent free blocks and remove them from buckets
|
||||
MergeAndRemoveFromBuckets(e);
|
||||
|
||||
// Step 5: Calculate final level and segment for the merged block
|
||||
uint finalSize = e->Size;
|
||||
int finalLevel = GetLevelFromSize(finalSize);
|
||||
int finalSegment = DetermineSegmentIndex(finalSize, finalLevel);
|
||||
|
||||
// Step 6: Set the merged entry state to Empty (ready to be reused)
|
||||
SetEntryState(e, EntryState.Empty);
|
||||
|
||||
// Step 7: Push the merged entry back to appropriate bucket
|
||||
PushLevel(finalLevel, finalSegment, (BucketNode*)e->Position);
|
||||
|
||||
// Note: If mergedEntry is different from original e, the original e
|
||||
// and any merged entries have already been freed to _entries by MergeAndRemoveFromBuckets
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Step 8: Always release the spin lock
|
||||
ReleaseEntrySpinLock(e);
|
||||
}
|
||||
|
||||
|
||||
// Set to ThreadOccupied to prevent other operations
|
||||
e->State = EntryState.Empty;
|
||||
|
||||
|
||||
// Try merge with adjacent free blocks and remove them from buckets
|
||||
MergeAndRemoveFromBuckets(e);
|
||||
|
||||
|
||||
// Calculate final level and segment for the merged block
|
||||
uint finalSize = e->Size;
|
||||
int finalLevel = GetLevelFromSize(finalSize);
|
||||
int finalSegment = DetermineSegmentIndex(finalSize, finalLevel);
|
||||
|
||||
|
||||
// Set the merged entry state to Empty (ready to be reused)
|
||||
e->State = EntryState.Empty;
|
||||
|
||||
|
||||
// Push the merged entry back to appropriate bucket
|
||||
PushLevel(finalLevel, finalSegment, (BucketNode*)e->Position);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,18 +41,24 @@ namespace StgSharp.HighPerformance.Memory
|
||||
public unsafe partial class HybridLayerSegregatedFitAllocator
|
||||
{
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireEntrySpinLock(Entry* entry)
|
||||
{
|
||||
AcquireEntryBothLocks(entry);
|
||||
}
|
||||
|
||||
private bool AssertBackwardBoundary(ulong current, Entry* target, int level)
|
||||
{
|
||||
ulong align = (ulong)levelSizeArray[level + 1];
|
||||
ulong targetOffset = target->Position - (ulong)m_Buffer;
|
||||
return current / align == targetOffset / align;
|
||||
int size = (level * 2) + 6;
|
||||
ulong align = current >> size;
|
||||
return align == target->Position >> size && align == (current + target->Size) >> size;
|
||||
}
|
||||
|
||||
private bool AssertForwardBoundary(ulong current, Entry* target, int level)
|
||||
{
|
||||
ulong align = (ulong)levelSizeArray[level + 1];
|
||||
ulong targetOffset = target->Position + target->Size - (ulong)m_Buffer;
|
||||
return current / align == targetOffset / align;
|
||||
int size = (level * 2) + 6;
|
||||
ulong align = current >> size;
|
||||
return align == target->Position >> size && align == (current - target->Size) >> size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +83,8 @@ namespace StgSharp.HighPerformance.Memory
|
||||
|
||||
/// <summary>
|
||||
/// Used in Free operations, efficiently remove from buckets and merge adjacent blocks.
|
||||
/// Current Entry will serve as the final merged block.
|
||||
/// Current Entry will serve as the final merged block. The Merge will be in lazy
|
||||
/// strategy, any fail in acquiring spin lock will end a merging process.
|
||||
/// </summary>
|
||||
private void MergeAndRemoveFromBuckets(Entry* entry)
|
||||
{
|
||||
@@ -85,131 +92,195 @@ namespace StgSharp.HighPerformance.Memory
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* result = entry;
|
||||
uint size = entry->Size;
|
||||
int originLevel = GetLevelFromSize(size);
|
||||
int segment = DetermineSegmentIndex(size, originLevel);
|
||||
|
||||
RemoveFromBucket(originLevel, segment, (BucketNode*)entry->Position);
|
||||
|
||||
while (originLevel >= entry->Level)
|
||||
if (!TryAcquireEntrySpinLock(entry)) {
|
||||
return;
|
||||
}
|
||||
if (!TryRemoveFromBucket(originLevel, segment, (BucketNode*)entry->Position)) { }
|
||||
try
|
||||
{
|
||||
originLevel = entry->Level;
|
||||
while (true)
|
||||
_ = TrySetEntryState(entry, EntryState.Empty, EntryState.Merging);
|
||||
|
||||
// current Level merge
|
||||
while (originLevel <= entry->Level)
|
||||
{
|
||||
// backward merging - simplified to single attempt
|
||||
ulong p = entry->PreviousNear;
|
||||
Entry* prev = (Entry*)p;
|
||||
if (prev == null ||
|
||||
prev == _spareMemory ||
|
||||
!AssertBackwardBoundary(entry->Position, prev, originLevel) ||
|
||||
IsAdjacentTo(entry, prev) ||
|
||||
entry->Level != prev->Level ||
|
||||
!TryRemoveFromBucket(originLevel, DetermineSegmentIndex(prev->Size, originLevel), (BucketNode*)prev->Position))
|
||||
ulong offset = entry->Position - (ulong)m_Buffer;
|
||||
|
||||
// backward merging
|
||||
while (true)
|
||||
{
|
||||
ulong p = entry->PreviousNear;
|
||||
Entry* prev = (Entry*)p;
|
||||
if (prev == null || // null ref
|
||||
prev == _spareMemory || // point at end of list
|
||||
GetEntryState(prev) != EntryState.Empty || // not empty
|
||||
!IsAdjacentTo(entry, prev) || // not nearby
|
||||
entry->Level != prev->Level || // not same level
|
||||
!AssertBackwardBoundary(offset, prev, entry->Level) || // not aligned if merge
|
||||
!TryAcquireEntrySpinLock(prev)) // occupied by threads
|
||||
{
|
||||
break;
|
||||
}
|
||||
ulong pp = prev->NextNear;
|
||||
Entry* prevPrev = (Entry*)pp;
|
||||
if (!TryAcquireEntryNextLock(prevPrev))
|
||||
{
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!TryRemoveFromBucket(originLevel, DetermineSegmentIndex(prev->Size, originLevel), (BucketNode*)prev->Position))
|
||||
{
|
||||
break;
|
||||
}
|
||||
entry->Position = prev->Position; // Update position to the previous entry
|
||||
entry->Size += prev->Size; // Merge sizes
|
||||
prevPrev->NextNear = (ulong)entry;
|
||||
RecycleEntry(prev);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseEntryNextLock(prevPrev);
|
||||
ReleaseEntrySpinLock(prev);
|
||||
}
|
||||
}
|
||||
|
||||
// forward merging
|
||||
while (true)
|
||||
{
|
||||
ulong n = entry->NextNear;
|
||||
Entry* next = (Entry*)n;
|
||||
if (next == null || // null ref
|
||||
next == _spareMemory || // point at end of list
|
||||
GetEntryState(next) != EntryState.Empty || // not empty
|
||||
!IsAdjacentTo(entry, next) || // not nearby
|
||||
entry->Level != next->Level || // not same level
|
||||
!AssertBackwardBoundary(offset, next, entry->Level) || // not aligned if merge
|
||||
!TryAcquireEntrySpinLock(next)) // occupied by threads
|
||||
{
|
||||
break;
|
||||
}
|
||||
ulong nn = next->NextNear;
|
||||
Entry* nextNext = (Entry*)nn;
|
||||
if (!TryAcquireEntryPrevLock(nextNext))
|
||||
{
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!TryRemoveFromBucket(originLevel, DetermineSegmentIndex(next->Size, originLevel), (BucketNode*)next->Position))
|
||||
{
|
||||
break;
|
||||
}
|
||||
entry->Size += next->Size;
|
||||
entry->NextNear = next->NextNear;
|
||||
RecycleEntry(next);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseEntrySpinLock(next);
|
||||
}
|
||||
}
|
||||
|
||||
entry->Level = GetLevelFromSize(entry->Size);
|
||||
if (originLevel == entry->Level)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Update linked list
|
||||
Entry* prevPrev = (Entry*)prev->PreviousNear;
|
||||
if (prevPrev != null) {
|
||||
prevPrev->NextNear = (ulong)entry;
|
||||
}
|
||||
entry->PreviousNear = (ulong)prevPrev;
|
||||
|
||||
// Merge
|
||||
entry->Position = prev->Position;
|
||||
entry->Size += prev->Size;
|
||||
RecycleEntry(prev);
|
||||
originLevel = entry->Level;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// forward merging - simplified to single attempt
|
||||
ulong n = entry->NextNear;
|
||||
Entry* next = (Entry*)n;
|
||||
if (next == null ||
|
||||
next == _spareMemory ||
|
||||
!AssertForwardBoundary(entry->Position + entry->Size, next, entry->Level) ||
|
||||
IsAdjacentTo(entry, next) ||
|
||||
entry->Level != next->Level ||
|
||||
!TryRemoveFromBucket(originLevel, DetermineSegmentIndex(next->Size, originLevel), (BucketNode*)next->Position))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Update linked list
|
||||
Entry* nextNext = (Entry*)next->NextNear;
|
||||
if (nextNext != null) {
|
||||
nextNext->PreviousNear = (ulong)entry;
|
||||
}
|
||||
entry->NextNear = (ulong)nextNext;
|
||||
|
||||
// Merge
|
||||
entry->Size += next->Size;
|
||||
RecycleEntry(next);
|
||||
}
|
||||
entry->Level = GetLevelFromSize(entry->Size);
|
||||
_ = TrySetEntryState(entry, EntryState.Merging, EntryState.Empty);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseEntrySpinLock(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecycleEntry(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
if (entry is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry->PreviousNear = EmptyHandle;
|
||||
entry->NextNear = EmptyHandle;
|
||||
AcquireEntrySpinLock(entry);
|
||||
_entries.Free((nuint)entry);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseEntrySpinLock(Entry* entry)
|
||||
{
|
||||
ReleaseEntryBothLocks(entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove node from position linked list
|
||||
/// </summary>
|
||||
private void RemoveFromPositionChain(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
AcquireEntrySpinLock(entry);
|
||||
try
|
||||
{
|
||||
Entry* prev = (Entry*)entry->PreviousNear;
|
||||
Entry* next = (Entry*)entry->NextNear;
|
||||
|
||||
Entry* prev = (Entry*)entry->PreviousNear;
|
||||
Entry* next = (Entry*)entry->NextNear;
|
||||
if (prev != null) {
|
||||
prev->NextNear = entry->NextNear;
|
||||
}
|
||||
if (next != null) {
|
||||
next->PreviousNear = entry->PreviousNear;
|
||||
}
|
||||
|
||||
if (prev != null) {
|
||||
prev->NextNear = entry->NextNear;
|
||||
// Clear node pointers
|
||||
entry->NextNear = EmptyHandle;
|
||||
entry->PreviousNear = EmptyHandle;
|
||||
_entries.Free((nuint)entry);
|
||||
}
|
||||
if (next != null) {
|
||||
next->PreviousNear = entry->PreviousNear;
|
||||
finally
|
||||
{
|
||||
ReleaseEntrySpinLock(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear node pointers
|
||||
entry->NextNear = EmptyHandle;
|
||||
entry->PreviousNear = EmptyHandle;
|
||||
_entries.Free((nuint)entry);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireEntrySpinLock(Entry* entry)
|
||||
{
|
||||
return TryAcquireEntryBothLocks(entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry structure - streamlined version (only position and state information) Stored in
|
||||
/// external SLAB
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 36)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = 44)]
|
||||
internal struct Entry
|
||||
{
|
||||
|
||||
[FieldOffset(28)] internal int Level; // Level of the block in the allocator
|
||||
[FieldOffset(32)] internal int NextLock;
|
||||
[FieldOffset(36)] internal int PrevLock;
|
||||
[FieldOffset(40)] internal int State; // Node state (using int for atomic operations)
|
||||
[FieldOffset(24)] internal uint Size; // Block size information
|
||||
[FieldOffset(32)] internal ulong LockBuffer;
|
||||
[FieldOffset(8)] internal ulong NextNear; // Position linked list: next pointer
|
||||
[FieldOffset(16)] internal ulong PreviousNear; // Position linked list: previous pointer
|
||||
[FieldOffset(32)] public EntryState State;
|
||||
[FieldOffset(0)] public nuint Position; // Memory block position
|
||||
|
||||
}
|
||||
|
||||
internal enum EntryState : uint
|
||||
// 更新后的EntryState常量类 - 使用int类型
|
||||
private static class EntryState
|
||||
{
|
||||
|
||||
Empty,
|
||||
Allocated
|
||||
public const int Allocated = 1; // Already allocated to user
|
||||
public const int Empty = 0; // Free, can be allocated
|
||||
public const int Merging = 3; // Currently being merged
|
||||
public const int ThreadOccupied = 2; // Occupied by thread (being operated on)
|
||||
|
||||
}
|
||||
|
||||
@@ -227,6 +298,176 @@ namespace StgSharp.HighPerformance.Memory
|
||||
return entry == null;
|
||||
}
|
||||
|
||||
// Entry 状态操作
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TrySetEntryState(Entry* entry, int expectedState, int newState)
|
||||
{
|
||||
return Interlocked.CompareExchange(ref entry->State, newState, expectedState) == expectedState;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetEntryState(Entry* entry)
|
||||
{
|
||||
return entry != null ? Volatile.Read(ref entry->State) : 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void SetEntryState(Entry* entry, int newState)
|
||||
{
|
||||
if (entry != null) {
|
||||
Volatile.Write(ref entry->State, newState);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsEntryEmpty(Entry* entry)
|
||||
{
|
||||
return GetEntryState(entry) == EntryState.Empty;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsEntryAllocated(Entry* entry)
|
||||
{
|
||||
return GetEntryState(entry) == EntryState.Allocated;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsEntryThreadOccupied(Entry* entry)
|
||||
{
|
||||
return GetEntryState(entry) == EntryState.ThreadOccupied;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsEntryMerging(Entry* entry)
|
||||
{
|
||||
return GetEntryState(entry) == EntryState.Merging;
|
||||
}
|
||||
|
||||
// Entry 前驱锁操作(控制PreviousNear字段)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireEntryPrevLock(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
return Interlocked.CompareExchange(ref entry->PrevLock, thread, 0) == 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireEntryPrevLock(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref entry->PrevLock) != thread)
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref entry->PrevLock, thread, 0) != 0) {
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseEntryPrevLock(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref entry->PrevLock) == thread) {
|
||||
Volatile.Write(ref entry->PrevLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireEntryNextLock(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
return Interlocked.CompareExchange(ref entry->NextLock, thread, 0) == 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireEntryNextLock(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref entry->NextLock) != thread)
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref entry->NextLock, thread, 0) != 0) {
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseEntryNextLock(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int thread = Environment.CurrentManagedThreadId;
|
||||
if (Volatile.Read(ref entry->NextLock) == thread) {
|
||||
Volatile.Write(ref entry->NextLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Entry 双锁组合操作
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireEntryBothLocks(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryAcquireEntryPrevLock(entry))
|
||||
{
|
||||
if (TryAcquireEntryNextLock(entry))
|
||||
{
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
ReleaseEntryPrevLock(entry); // 回滚
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReleaseEntryBothLocks(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReleaseEntryPrevLock(entry);
|
||||
ReleaseEntryNextLock(entry);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AcquireEntryBothLocks(Entry* entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AcquireEntryPrevLock(entry);
|
||||
AcquireEntryNextLock(entry);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace StgSharp.HighPerformance.Memory
|
||||
if (byteSize < 64) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
_entries = SlabAllocator<Entry>.Create(64, SlabBufferLayout.Chunked, false);
|
||||
_entries = SlabAllocator<Entry>.Create(64, BufferLayout.Chunked);
|
||||
_size = byteSize;
|
||||
m_Buffer = (byte*)NativeMemory.AlignedAlloc(byteSize, 16);
|
||||
_align = 16;
|
||||
@@ -62,10 +62,12 @@ namespace StgSharp.HighPerformance.Memory
|
||||
InitializeBucketSystem();
|
||||
|
||||
_spareMemory = (Entry*)_entries.Allocate();
|
||||
_spareMemory->State = EntryState.Empty;
|
||||
SetEntryState(_spareMemory, EntryState.ThreadOccupied);
|
||||
_spareMemory->PreviousNear = EmptyHandle;
|
||||
_spareMemory->NextNear = EmptyHandle;
|
||||
_spareMemory->Position = (nuint)m_Buffer;
|
||||
_spareMemory->PrevLock = 0;
|
||||
_spareMemory->NextLock = 0;
|
||||
_spareMemory->Size = (uint)byteSize;
|
||||
}
|
||||
|
||||
@@ -75,7 +77,7 @@ namespace StgSharp.HighPerformance.Memory
|
||||
throw new ArgumentException(
|
||||
"Alignment must be a power of two and at least 8 bytes.");
|
||||
}
|
||||
_entries = SlabAllocator<Entry>.Create(64, SlabBufferLayout.Chunked);
|
||||
_entries = SlabAllocator<Entry>.Create(64, BufferLayout.Chunked);
|
||||
m_Buffer = (byte*)NativeMemory.AlignedAlloc(byteSize, (nuint)align);
|
||||
_align = align;
|
||||
_size = byteSize;
|
||||
@@ -83,10 +85,12 @@ namespace StgSharp.HighPerformance.Memory
|
||||
InitializeBucketSystem();
|
||||
|
||||
_spareMemory = (Entry*)_entries.Allocate();
|
||||
_spareMemory->State = EntryState.Empty;
|
||||
SetEntryState(_spareMemory, EntryState.ThreadOccupied);
|
||||
_spareMemory->PreviousNear = EmptyHandle;
|
||||
_spareMemory->NextNear = EmptyHandle;
|
||||
_spareMemory->Position = (nuint)m_Buffer;
|
||||
_spareMemory->PrevLock = 0;
|
||||
_spareMemory->NextLock = 0;
|
||||
_spareMemory->Size = (uint)byteSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="ISlabAllocatable.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public interface ISlabAllocatable { }
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
# HLSF (Hybrid Layer Segregated Fit) Allocator
|
||||
|
||||
A high-performance, O(1) memory allocator designed for real-time applications that demand predictable allocation and deallocation performance.
|
||||
|
||||
## Overview
|
||||
|
||||
The Hybrid Layer Segregated Fit (HLSF) allocator is a sophisticated memory management system that combines the benefits of segregated free lists with layered size classification. It provides constant-time allocation and deallocation operations while maintaining excellent memory utilization and low fragmentation.
|
||||
|
||||
## Key Features
|
||||
|
||||
### Performance Characteristics
|
||||
- **O(1) Time Complexity**: Both allocation and deallocation operations execute in constant time
|
||||
- **High Throughput**: Capable of handling millions of operations per second
|
||||
- **Low Latency**: Predictable performance suitable for real-time applications
|
||||
- **Excellent Deallocation Speed**: Superior free performance compared to traditional allocators
|
||||
|
||||
### Memory Management
|
||||
- **External Metadata Storage**: All allocation metadata is stored in external SLAB allocators, keeping the main allocation area clean
|
||||
- **Native Byte Alignment Control**: Configurable alignment (minimum 16 bytes) with hardware-optimized defaults
|
||||
- **Large Block Support**: Single allocation limit of 32MB per request
|
||||
- **Efficient Memory Utilization**: Low overhead with intelligent block merging
|
||||
|
||||
### Architecture
|
||||
- **10-Level Hierarchy**: Size classes from 64 bytes to 16MB (64, 256, 1KB, 4KB, 16KB, 64KB, 256KB, 1MB, 4MB, 16MB)
|
||||
- **3-Segment Segregation**: Each level is divided into 3 segments for better size distribution
|
||||
- **Hybrid Approach**: Combines best aspects of segregated fit and layered allocation strategies
|
||||
|
||||
## Architecture Details
|
||||
|
||||
### Size Classification System
|
||||
|
||||
The allocator uses a logarithmic size classification system with 10 levels:
|
||||
|
||||
```
|
||||
Level 0: 64 bytes (2^6)
|
||||
Level 1: 256 bytes (2^8)
|
||||
Level 2: 1KB (2^10)
|
||||
Level 3: 4KB (2^12)
|
||||
Level 4: 16KB (2^14)
|
||||
Level 5: 64KB (2^16)
|
||||
Level 6: 256KB (2^18)
|
||||
Level 7: 1MB (2^20)
|
||||
Level 8: 4MB (2^22)
|
||||
Level 9: 16MB (2^24)
|
||||
```
|
||||
|
||||
Each level follows the pattern: `size = 64 * 4^level`
|
||||
|
||||
### Bucket Organization
|
||||
|
||||
Each size level is further divided into 3 segments based on exact size requirements:
|
||||
- **Segment 0**: 1x level size
|
||||
- **Segment 1**: 2x level size
|
||||
- **Segment 2**: 3x level size
|
||||
|
||||
This provides fine-grained size matching while maintaining O(1) lookup performance.
|
||||
|
||||
### External Metadata Design
|
||||
|
||||
Unlike traditional allocators that embed metadata within allocated blocks, HLSF stores all metadata externally:
|
||||
|
||||
- **Entry Structure (36 bytes)**: Contains position, size, level, and linkage information
|
||||
- **BucketNode Structure (32 bytes)**: Manages free block organization with integrity verification
|
||||
- **SLAB Storage**: Both structures are allocated from separate SLAB allocators for optimal cache performance
|
||||
|
||||
This design provides several advantages:
|
||||
- Clean allocation areas free from metadata corruption
|
||||
- Better cache locality for metadata operations
|
||||
- Simplified debugging and memory inspection
|
||||
- No metadata overhead in allocated blocks
|
||||
|
||||
## Performance Benchmarks
|
||||
|
||||
*Note: The following results are from informal testing and should be considered preliminary.*
|
||||
|
||||
### Single-threaded Performance (65,536 operations)
|
||||
|
||||
| Operation | Average Time | Throughput | Performance Rating |
|
||||
|-----------|--------------|------------|-------------------|
|
||||
| Allocation | 31-40ms | ~2M allocs/sec | Good |
|
||||
| Free | 2-3ms | ~25M frees/sec | Excellent |
|
||||
|
||||
**Test Configuration:**
|
||||
- Pool Size: 16MB
|
||||
- Block Sizes: 64-1024 bytes (mixed)
|
||||
- Platform: x64, .NET 8
|
||||
- Iterations: 5 rounds
|
||||
|
||||
### Memory Efficiency
|
||||
|
||||
- **Metadata Overhead**: ~2.8% of total allocation
|
||||
- **Alignment**: 16-byte default (configurable)
|
||||
- **Fragmentation**: Low due to intelligent merging algorithms
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Allocation
|
||||
|
||||
```csharp
|
||||
// Create allocator with 64MB pool
|
||||
using var allocator = new HybridLayerSegregatedFitAllocator(64 * 1024 * 1024);
|
||||
|
||||
// Allocate 1KB block
|
||||
var handle = allocator.Alloc(1024);
|
||||
|
||||
// Use the allocated memory
|
||||
Span<byte> memory = handle.BufferHandle;
|
||||
memory[0] = 42;
|
||||
|
||||
// Free the block
|
||||
allocator.Free(handle);
|
||||
```
|
||||
|
||||
### Custom Alignment
|
||||
|
||||
```csharp
|
||||
// Create allocator with 32-byte alignment
|
||||
using var allocator = new HybridLayerSegregatedFitAllocator(
|
||||
byteSize: 32 * 1024 * 1024,
|
||||
align: 32
|
||||
);
|
||||
|
||||
var handle = allocator.Alloc(256);
|
||||
// Memory is guaranteed to be 32-byte aligned
|
||||
```
|
||||
|
||||
### Bulk Operations
|
||||
|
||||
```csharp
|
||||
var handles = new List<HybridLayerSegregatedFitAllocationHandle>();
|
||||
|
||||
// Allocate multiple blocks
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
handles.Add(allocator.Alloc((uint)(64 + i % 960))); // 64-1023 bytes
|
||||
}
|
||||
|
||||
// Free all blocks
|
||||
foreach (var handle in handles)
|
||||
{
|
||||
allocator.Free(handle);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### HybridLayerSegregatedFitAllocator
|
||||
|
||||
Main allocator class providing memory management functionality.
|
||||
|
||||
#### Constructors
|
||||
|
||||
```csharp
|
||||
public HybridLayerSegregatedFitAllocator(nuint byteSize)
|
||||
public HybridLayerSegregatedFitAllocator(nuint byteSize, int align)
|
||||
```
|
||||
|
||||
#### Methods
|
||||
|
||||
```csharp
|
||||
public HybridLayerSegregatedFitAllocationHandle Alloc(uint size)
|
||||
public void Free(HybridLayerSegregatedFitAllocationHandle handle)
|
||||
public void Dispose()
|
||||
```
|
||||
|
||||
### HybridLayerSegregatedFitAllocationHandle
|
||||
|
||||
Handle representing an allocated memory block.
|
||||
|
||||
#### Properties
|
||||
|
||||
```csharp
|
||||
public readonly byte* Pointer { get; }
|
||||
public readonly Span<byte> BufferHandle { get; }
|
||||
public readonly uint AllocSize { get; }
|
||||
public readonly ref byte this[int index] { get; }
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The current implementation is **single-threaded**. For multi-threaded applications, external synchronization is required. A concurrent version is planned for future releases.
|
||||
|
||||
## Comparison with Other Allocators
|
||||
|
||||
### vs TLSF (Two-Level Segregated Fit)
|
||||
|
||||
| Aspect | HLSF | TLSF | Notes |
|
||||
|--------|------|------|-------|
|
||||
| Allocation Speed | ~2M ops/sec | ~3-5M ops/sec | HLSF has optimization potential |
|
||||
| Deallocation Speed | ~25M ops/sec | ~10-15M ops/sec | **HLSF advantage** |
|
||||
| Memory Overhead | ~2.8% | ~1-2% | HLSF uses external metadata |
|
||||
| Time Complexity | O(1) | O(1) | Equal |
|
||||
| Code Complexity | Moderate | Low | HLSF more sophisticated |
|
||||
|
||||
### Strengths
|
||||
- Superior deallocation performance
|
||||
- Clean separation of data and metadata
|
||||
- Configurable alignment support
|
||||
- Excellent for allocation-heavy workloads
|
||||
|
||||
### Areas for Improvement
|
||||
- Allocation speed optimization
|
||||
- Memory overhead reduction
|
||||
- Concurrent implementation
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Alignment Guarantees
|
||||
|
||||
- Minimum alignment: 16 bytes
|
||||
- All allocations respect specified alignment
|
||||
- Internal blocks aligned to 64-byte boundaries for optimal cache performance
|
||||
|
||||
### Merging Strategy
|
||||
|
||||
The allocator employs an aggressive merging strategy:
|
||||
|
||||
1. **Immediate Merging**: Adjacent free blocks are merged during deallocation
|
||||
2. **Boundary Checking**: Ensures merging respects level boundaries
|
||||
3. **Bidirectional Merging**: Checks both forward and backward neighbors
|
||||
4. **Level Promotion**: Merged blocks may be promoted to higher levels
|
||||
|
||||
## Future Roadmap
|
||||
|
||||
### Planned Optimizations
|
||||
|
||||
1. **Performance Enhancements**
|
||||
- Lookup table optimization for size classification
|
||||
- Bucket operation improvements
|
||||
- SIMD utilization for bulk operations
|
||||
|
||||
2. **Concurrent Support**
|
||||
- Thread-local caches
|
||||
- Lock-free bucket operations
|
||||
- NUMA-aware allocation
|
||||
|
||||
3. **Advanced Features**
|
||||
- Adaptive size level adjustment
|
||||
- Memory pressure handling
|
||||
- Statistics and profiling support
|
||||
|
||||
### Target Performance Goals
|
||||
|
||||
- Allocation Speed: 4-6M ops/sec (single-threaded)
|
||||
- Memory Overhead: <1.5%
|
||||
- Fragmentation Rate: <3%
|
||||
- Concurrent Scaling: Linear up to 16 threads
|
||||
|
||||
## Building and Testing
|
||||
|
||||
The HLSF allocator is part of the StgSharp.Common library and requires:
|
||||
|
||||
- .NET 8 or later
|
||||
- x64 architecture (no x86 support planned)
|
||||
- Windows/Linux
|
||||
|
||||
### Running Benchmarks
|
||||
|
||||
```csharp
|
||||
// See StgSharpDebug/Program.cs for comprehensive benchmark suite
|
||||
var allocator = new HybridLayerSegregatedFitAllocator(16 * 1024 * 1024);
|
||||
// Run allocation/deallocation cycles...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This implementation is part of the StgSharp project and follows the project's licensing terms.
|
||||
|
||||
## Contributing
|
||||
|
||||
Performance improvements, bug fixes, and documentation enhancements are welcome. Please ensure all changes maintain the O(1) performance guarantees and include appropriate benchmarks.
|
||||
@@ -32,9 +32,12 @@ using StgSharp.Threading;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
@@ -42,8 +45,8 @@ namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
|
||||
private readonly BufferExpansionLock _lock = new();
|
||||
private BufferStack<nuint> _buffers;
|
||||
private BufferStack<nuint> _recycle;
|
||||
private ConcurrentStackBuffer<nuint> _buffers;
|
||||
private ConcurrentStackBuffer<nuint> _recycle;
|
||||
private readonly int _elementSize = Unsafe.SizeOf<T>();
|
||||
|
||||
private nuint _currentBuffer;
|
||||
@@ -64,7 +67,7 @@ namespace StgSharp.HighPerformance.Memory
|
||||
span[i] = _currentBuffer + (nuint)(i * _elementSize);
|
||||
}
|
||||
_buffers = new(4);
|
||||
_recycle = BufferStackBuilder.Create(span);
|
||||
_recycle = new(span);
|
||||
_currentCapacity = count;
|
||||
_currentIndex = (nuint)initCount;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="SlabAllocator.ChunkedConcurrent.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Threading;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
internal sealed unsafe class ConcurrentChunkedSlabAllocator<T> : SlabAllocator<T> where T: unmanaged
|
||||
{
|
||||
|
||||
private readonly BufferExpansionLock _lock = new();
|
||||
private ConcurrentBufferStack<nuint> _buffers;
|
||||
private ConcurrentBufferStack<nuint> _recycle;
|
||||
private readonly int _elementSize = Unsafe.SizeOf<T>();
|
||||
|
||||
private nuint _currentBuffer;
|
||||
private nuint _currentCapacity, _currentIndex;
|
||||
|
||||
public ConcurrentChunkedSlabAllocator(nuint count)
|
||||
{
|
||||
_currentBuffer = (nuint)NativeMemory.Alloc(count * (nuint)_elementSize);
|
||||
_currentCapacity = count;
|
||||
int initCount = count switch
|
||||
{
|
||||
<= 4 => 4,
|
||||
> 64 => 64,
|
||||
_ => (int)count
|
||||
};
|
||||
Span<nuint> span = stackalloc nuint[initCount];
|
||||
for (int i = 0; i < initCount; i++) {
|
||||
span[i] = _currentBuffer + (nuint)(i * _elementSize);
|
||||
}
|
||||
_buffers = new(4);
|
||||
_recycle = BufferStackBuilder.CreateConcurrent(span);
|
||||
_currentCapacity = count;
|
||||
_currentIndex = (nuint)initCount;
|
||||
}
|
||||
|
||||
public override nuint Allocate()
|
||||
{
|
||||
if (_recycle.TryPop(out nuint handle)) {
|
||||
return handle;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
nuint bufferHandle = Volatile.Read(ref _currentBuffer);
|
||||
nuint curIndex = Volatile.Read(ref _currentIndex);
|
||||
_lock.EnterMetaDataRead();
|
||||
try
|
||||
{
|
||||
if (curIndex >= _currentCapacity)
|
||||
{
|
||||
_lock.ExitMetaDataRead();
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
nuint cap = Volatile.Read(ref _currentCapacity);
|
||||
nuint newBuffer = (nuint)NativeMemory.Alloc(cap * (nuint)_elementSize);
|
||||
_buffers.Push(bufferHandle);
|
||||
_ = Interlocked.Exchange(ref _currentBuffer, newBuffer);
|
||||
_ = Interlocked.Exchange(ref _currentIndex, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
}
|
||||
curIndex = Volatile.Read(ref _currentIndex);
|
||||
_ = Interlocked.Add(
|
||||
ref Unsafe.As<nuint, ulong>(ref _currentIndex), (ulong)_elementSize);
|
||||
return _currentBuffer + ((nuint)_elementSize * curIndex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsThreadReadingMetaData) {
|
||||
_lock.ExitMetaDataRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
NativeMemory.Free((void*)_currentBuffer);
|
||||
while (_buffers.TryPop(out nuint buffer)) {
|
||||
NativeMemory.Free((void*)buffer);
|
||||
}
|
||||
_lock.Dispose();
|
||||
_buffers.Dispose();
|
||||
_recycle.Dispose();
|
||||
}
|
||||
|
||||
[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<T> ReadAllocation()
|
||||
{
|
||||
return new SlabAllocationHandle<T>(this, false);
|
||||
}
|
||||
|
||||
~ConcurrentChunkedSlabAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="SlabAllocator.Sequencial.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Threading;
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public sealed unsafe class SequentialSlabAllocator<T> : SlabAllocator<T> where T: unmanaged
|
||||
{
|
||||
|
||||
private T* _buffer;
|
||||
|
||||
private readonly BufferExpansionLock _lock = new();
|
||||
private readonly ConcurrentStackBuffer<nuint> _stack;
|
||||
private readonly int _elementSize;
|
||||
private nuint _highestAllocated;
|
||||
private ulong _capacity;
|
||||
|
||||
public SequentialSlabAllocator(nuint initialCapacity)
|
||||
{
|
||||
_elementSize = Unsafe.SizeOf<T>();
|
||||
_capacity = initialCapacity;
|
||||
_highestAllocated = 0;
|
||||
|
||||
_buffer = (T*)NativeMemory.Alloc((nuint)_capacity * (nuint)_elementSize);
|
||||
int initCount = _capacity switch
|
||||
{
|
||||
<= 4 => 4,
|
||||
> 64 => 64,
|
||||
_ => (int)_capacity / 4
|
||||
};
|
||||
Span<nuint> span = stackalloc nuint[initCount];
|
||||
for (int i = 0; i < initCount; i++) {
|
||||
span[i] = (nuint)i;
|
||||
}
|
||||
_stack = new(span);
|
||||
_highestAllocated += (nuint)initCount;
|
||||
}
|
||||
|
||||
internal T* BasePointer
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _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<nuint, ulong>(ref _highestAllocated));
|
||||
return index;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsThreadReadingMetaData) {
|
||||
_lock.ExitMetaDataRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_stack.Dispose();
|
||||
NativeMemory.Free(_buffer);
|
||||
_lock.Dispose();
|
||||
}
|
||||
|
||||
[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<T> ReadAllocation()
|
||||
{
|
||||
EnterBufferReading();
|
||||
return new SlabAllocationHandle<T>(this, true);
|
||||
}
|
||||
|
||||
~SequentialSlabAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="SlabAllocator.SequencialConcurrent.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Threading;
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public sealed unsafe class ConcurrentSequentialSlabAllocator<T> : SlabAllocator<T> where T: unmanaged
|
||||
{
|
||||
|
||||
private T* _buffer;
|
||||
|
||||
private readonly BufferExpansionLock _lock = new();
|
||||
private readonly ConcurrentBufferStack<nuint> _stack;
|
||||
private readonly int _elementSize;
|
||||
private nuint _highestAllocated;
|
||||
private ulong _capacity;
|
||||
|
||||
public ConcurrentSequentialSlabAllocator(nuint initialCapacity)
|
||||
{
|
||||
_elementSize = Unsafe.SizeOf<T>();
|
||||
_capacity = initialCapacity;
|
||||
_highestAllocated = 0;
|
||||
|
||||
_buffer = (T*)NativeMemory.Alloc((nuint)_capacity * (nuint)_elementSize);
|
||||
int initCount = _capacity switch
|
||||
{
|
||||
<= 4 => 4,
|
||||
> 64 => 64,
|
||||
_ => (int)_capacity / 4
|
||||
};
|
||||
Span<nuint> span = stackalloc nuint[initCount];
|
||||
for (int i = 0; i < initCount; i++) {
|
||||
span[i] = (nuint)i;
|
||||
}
|
||||
_stack = BufferStackBuilder.CreateConcurrent(span);
|
||||
_highestAllocated += (nuint)initCount;
|
||||
}
|
||||
|
||||
internal T* BasePointer
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _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<nuint, ulong>(ref _highestAllocated));
|
||||
return index;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsThreadReadingMetaData) {
|
||||
_lock.ExitMetaDataRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_stack.Dispose();
|
||||
NativeMemory.Free(_buffer);
|
||||
_lock.Dispose();
|
||||
}
|
||||
|
||||
[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<T> ReadAllocation()
|
||||
{
|
||||
EnterBufferReading();
|
||||
return new SlabAllocationHandle<T>(this, true);
|
||||
}
|
||||
|
||||
~ConcurrentSequentialSlabAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,17 @@
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using StgSharp.HighPerformance.Memory;
|
||||
using StgSharp.Threading;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
@@ -37,62 +47,12 @@ namespace StgSharp.HighPerformance.Memory
|
||||
|
||||
public abstract nuint Allocate();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new SLAB allocator instance with the specified configuration.
|
||||
/// </summary>
|
||||
/// <param name="count">
|
||||
/// Initial number of objects that can be allocated.
|
||||
/// </param>
|
||||
/// <param name="layout">
|
||||
/// Memory layout strategy for the SLAB allocator.
|
||||
/// </param>
|
||||
/// <param name="concurrentSupport">
|
||||
/// Whether to enable concurrent access support.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new SLAB allocator instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when an invalid layout is specified.
|
||||
/// </exception>
|
||||
/// <exception cref="NotSupportedException">
|
||||
/// Thrown when requesting unsupported configurations.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para><strong> Important Threading Considerations: </strong></para> <para> While SLAB
|
||||
/// allocators themselves may be designed for single-threaded use, allocated objects are
|
||||
/// frequently shared across multiple threads in real-world applications. Due to the
|
||||
/// special implementation of expandable concurrent sequential SLAB allocators, they rely
|
||||
/// on internal expansion locks to ensure that memory buffer expansions (which involve
|
||||
/// copying the entire buffer to a new location) do not occur while other threads are
|
||||
/// reading from allocated regions.</para> <para><strong> Sequential Layout Limitations:
|
||||
/// </strong><br /> Sequential layout SLAB allocators cannot provide a single-threaded
|
||||
/// version because:<list type="bullet"><item><description> They use contiguous memory
|
||||
/// layout that requires buffer expansion when capacity is exceeded
|
||||
/// </description></item><item><description> Buffer expansion involves copying all
|
||||
/// existing data to a new memory location </description></item><item><description>
|
||||
/// Without proper synchronization, concurrent access to allocated objects during
|
||||
/// expansion would cause memory corruption </description></item><item><description> The
|
||||
/// expansion lock mechanism is essential even if allocation itself is single-threaded
|
||||
/// </description></item></list></para> <para><strong> Chunked Layout: </strong><br />
|
||||
/// Chunked layout allocators can optionally provide single-threaded versions since they
|
||||
/// don't require buffer expansion and copying. Each chunk is allocated independently,
|
||||
/// avoiding the memory relocation issues present in sequential layouts.</para>
|
||||
/// </remarks>
|
||||
public static SlabAllocator<T> Create(nuint count, SlabBufferLayout layout, bool concurrentSupport = true)
|
||||
public static SlabAllocator<T> Create(nuint count, BufferLayout layout)
|
||||
{
|
||||
if (concurrentSupport) {
|
||||
return layout switch
|
||||
{
|
||||
SlabBufferLayout.Sequential => new ConcurrentSequentialSlabAllocator<T>(count),
|
||||
SlabBufferLayout.Chunked => new ConcurrentChunkedSlabAllocator<T>(count),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(layout), layout, null)
|
||||
};
|
||||
}
|
||||
return layout switch
|
||||
{
|
||||
SlabBufferLayout.Sequential => throw new NotSupportedException("Non-concurrent version of sequential layout SLAB is not supported due to expansion lock requirements for memory safety during buffer expansion."),
|
||||
SlabBufferLayout.Chunked => new ChunkedSlabAllocator<T>(count),
|
||||
BufferLayout.Sequential => new SequentialSlabAllocator<T>(count),
|
||||
BufferLayout.Chunked => new ChunkedSlabAllocator<T>(count),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(layout), layout, null)
|
||||
};
|
||||
}
|
||||
@@ -109,11 +69,12 @@ namespace StgSharp.HighPerformance.Memory
|
||||
|
||||
}
|
||||
|
||||
public enum SlabBufferLayout
|
||||
public enum BufferLayout
|
||||
{
|
||||
|
||||
Sequential,
|
||||
Chunked
|
||||
Chunked,
|
||||
HybridLayerSegregatedFit
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="UnmanagedMemoryHandle.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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 System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public unsafe struct UnmanagedMemoryHandle
|
||||
{
|
||||
|
||||
public readonly byte* BufferHandle, AllocatorHandle;
|
||||
public readonly nuint Size;
|
||||
|
||||
public UnmanagedMemoryHandle(byte* pointer, byte* handle, nuint s)
|
||||
{
|
||||
BufferHandle = pointer;
|
||||
AllocatorHandle = handle;
|
||||
Size = s;
|
||||
}
|
||||
|
||||
public readonly ref byte this[nuint index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref Unsafe.AddByteOffset(ref Unsafe.AsRef<byte>(BufferHandle), index);
|
||||
}
|
||||
|
||||
public readonly Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
public ref struct Enumerator : IEnumerator<byte>
|
||||
{
|
||||
|
||||
private nuint _index = nuint.MaxValue;
|
||||
|
||||
private UnmanagedMemoryHandle _handle;
|
||||
|
||||
internal Enumerator(UnmanagedMemoryHandle span)
|
||||
{
|
||||
_handle = span;
|
||||
}
|
||||
|
||||
public ref byte RefCurrent => ref _handle[_index];
|
||||
|
||||
public readonly void Dispose() { }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
return _index < _handle.Size;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
object IEnumerator.Current => RefCurrent;
|
||||
|
||||
byte IEnumerator<byte>.Current => RefCurrent;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="UnmanagedMemoryPool.BucketTLSF.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.HighPerformance.Memory;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public unsafe partial class UnmanagedMemoryPool
|
||||
{
|
||||
|
||||
private struct Entry : ISlabAllocatable
|
||||
{
|
||||
|
||||
public Entry* NextNear, PreviousNear;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="UnmanagedMemoryPool.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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 System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Drawing;
|
||||
using System.Net.Security;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Transactions;
|
||||
|
||||
namespace StgSharp.HighPerformance.Memory
|
||||
{
|
||||
public unsafe partial class UnmanagedMemoryPool
|
||||
{
|
||||
|
||||
private byte* m_Buffer;
|
||||
private SlabAllocator<Entry> _nodes = new(64);
|
||||
|
||||
public
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user