switch root namespace name to NITLOAD
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="BufferStack"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nitload.Common.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
|
||||
)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
|
||||
|
||||
AllocateBuffer(capacity);
|
||||
}
|
||||
|
||||
public int Count => (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();
|
||||
header = (Header*)_buffer;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the stack in place via a caller-supplied callback, mirroring <c>
|
||||
/// CapacityFixedStack<T>.FillRange </c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Preferred over building a temporary span and handing it to <see
|
||||
/// cref="BufferStackBuilder.Create{T}(ReadOnlySpan{T})" />: <list type="bullet"> ///
|
||||
/// <item> The caller no longer needs a scratch buffer at all — the /// callback writes
|
||||
/// straight into this stack's native storage, so there /// is no <c> stackalloc </c> to
|
||||
/// size and no copy afterwards. </item> /// <item><c> Span<T> </c> does not
|
||||
/// implicitly convert to /// <c> ReadOnlySpan<T> </c> in a way that participates in
|
||||
/// generic /// type inference before C# 14, so <c> Create(span) </c> fails with ///
|
||||
/// CS0411 on older compilers. Passing the element type through the /// enclosing instance
|
||||
/// sidesteps inference entirely. </item> ///</list>
|
||||
/// </remarks>
|
||||
internal unsafe void FillRange(
|
||||
SpanAction<T, nuint> filling,
|
||||
nuint buffer
|
||||
)
|
||||
{
|
||||
if (filling is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Header* header = (Header*)_buffer;
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
|
||||
// Hand out only the unused tail, matching CapacityFixedStack:
|
||||
// filling is an append, not a rewrite of live entries.
|
||||
int start = (int)header->Top;
|
||||
int room = (int)header->Capacity - start;
|
||||
if (room <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
filling(new Span<T>(dataStart + start, room), buffer);
|
||||
|
||||
header->Top = header->Capacity;
|
||||
_count = header->Capacity;
|
||||
}
|
||||
|
||||
// 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,343 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="ConcurrentBufferStack"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Threading;
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nitload.Common.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(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(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 long _count;
|
||||
|
||||
public ConcurrentBufferStack()
|
||||
{
|
||||
AllocateBuffer(4);
|
||||
}
|
||||
|
||||
public ConcurrentBufferStack(
|
||||
int capacity
|
||||
)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
|
||||
|
||||
AllocateBuffer(capacity);
|
||||
}
|
||||
|
||||
public ulong Count => (ulong)Volatile.Read(ref _count);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
_lock.EnterBufferCopy(); // exclusive while mutating Top
|
||||
try
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
header->Top = 0;
|
||||
Volatile.Write(ref _count, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferCopy();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_lock.Dispose();
|
||||
NativeMemory.Free(_buffer);
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_lock.EnterBufferRead(); // prevent buffer being reallocated while reading/modifying Top
|
||||
try
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
int oldTop = Volatile.Read(ref header->Top);
|
||||
if (oldTop <= 0) {
|
||||
throw new InvalidOperationException("Stack underflow");
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop)
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
_ = Interlocked.Decrement(ref _count);
|
||||
return dataStart[oldTop - 1];
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void Push(
|
||||
T item
|
||||
)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Ensure capacity, expanding if needed
|
||||
_lock.EnterMetaDataRead();
|
||||
try
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
int topSnapshot = Volatile.Read(ref header->Top);
|
||||
int cap = Volatile.Read(ref header->Capacity);
|
||||
if (topSnapshot >= cap)
|
||||
{
|
||||
// Need expansion
|
||||
_lock.ExitMetaDataRead();
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
header = (Header*)_buffer;
|
||||
if (header->Top >= header->Capacity)
|
||||
{
|
||||
int newCap = header->Capacity * 2;
|
||||
nuint newSize = ((nuint)sizeof(Header)) + (((nuint)newCap) * ((nuint)sizeof(T)));
|
||||
|
||||
_lock.EnterBufferCopy();
|
||||
try
|
||||
{
|
||||
_buffer = (byte*)NativeMemory.Realloc(_buffer, newSize);
|
||||
((Header*)_buffer)->Capacity = newCap;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferCopy();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsThreadReadingMetaData) {
|
||||
_lock.ExitMetaDataRead();
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve and write under exclusive buffer lock to avoid consumers reading unwritten data
|
||||
_lock.EnterBufferCopy();
|
||||
try
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
int idx = header->Top; // reserve next slot
|
||||
if (idx >= header->Capacity)
|
||||
{
|
||||
// lost race against expander or other producer; retry
|
||||
continue;
|
||||
}
|
||||
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
dataStart[idx] = item; // write first
|
||||
header->Top = idx + 1; // then publish by increasing top
|
||||
_ = Interlocked.Increment(ref _count);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferCopy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPop(
|
||||
out T value
|
||||
)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
try
|
||||
{
|
||||
Header* header = (Header*)_buffer;
|
||||
int oldTop = Volatile.Read(ref header->Top);
|
||||
if (oldTop <= 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop)
|
||||
{
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
value = dataStart[oldTop - 1];
|
||||
_ = Interlocked.Decrement(ref _count);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the stack in place via a caller-supplied callback, mirroring
|
||||
/// <c>CapacityFixedStack<T>.FillRange</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Intended for construction only, and therefore takes no lock — the
|
||||
/// instance is not yet published to other threads at that point. Do not
|
||||
/// call it on a stack that is already in concurrent use.
|
||||
/// <para>
|
||||
/// Preferred over building a temporary span and handing it to
|
||||
/// <see cref="BufferStackBuilder.CreateConcurrent{T}(ReadOnlySpan{T})"/>
|
||||
/// for the same two reasons as the non-concurrent variant: the callback
|
||||
/// writes straight into native storage (no scratch buffer, no copy),
|
||||
/// and passing the element type through the enclosing instance avoids
|
||||
/// the <c>Span</c>-to-<c>ReadOnlySpan</c> inference gap that yields
|
||||
/// CS0411 before C# 14.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal unsafe void FillRange(
|
||||
SpanAction<T, nuint> filling,
|
||||
nuint buffer
|
||||
)
|
||||
{
|
||||
if (filling is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Header* header = (Header*)_buffer;
|
||||
T* dataStart = (T*)(_buffer + sizeof(Header));
|
||||
|
||||
// Append into the unused tail only, matching CapacityFixedStack.
|
||||
int start = header->Top;
|
||||
int room = header->Capacity - start;
|
||||
if (room <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
filling(new Span<T>(dataStart + start, room), buffer);
|
||||
|
||||
header->Top = header->Capacity;
|
||||
Volatile.Write(ref _count, header->Capacity);
|
||||
}
|
||||
|
||||
// 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,
|
||||
((nuint)header->Capacity) * ((nuint)sizeof(T)),
|
||||
((nuint)span.Length) * ((nuint)sizeof(T)));
|
||||
}
|
||||
|
||||
header->Top = span.Length;
|
||||
Volatile.Write(ref _count, 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 = capacity;
|
||||
}
|
||||
|
||||
private struct Header
|
||||
{
|
||||
|
||||
public int Capacity; // max capacity of the stack (number of elements)
|
||||
public int Top; // next available index in the stack
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.CacheLine"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.HighPerformance.ProcessorAbstraction;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public partial class L4
|
||||
{
|
||||
|
||||
internal enum AllocationState : byte
|
||||
{
|
||||
|
||||
SpareOrCollect = 0,
|
||||
Free = 1,
|
||||
Alloc = 2,
|
||||
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 32, Pack = 32)]
|
||||
private unsafe struct CacheLineMetadata
|
||||
{
|
||||
|
||||
internal const uint MaxBufferSize = 0xFFFFFFFF - 64U;
|
||||
|
||||
[FieldOffset(0)] private readonly M256 FillingMask;
|
||||
|
||||
[FieldOffset(24)] internal nuint Origin;
|
||||
|
||||
public static uint SizeOf { get; } = (uint)Unsafe.SizeOf<CacheLineMetadata>();
|
||||
|
||||
#region tlsf linked list
|
||||
|
||||
[FieldOffset(0)] public short NextLevel;
|
||||
[FieldOffset(2)] public short NextNear;
|
||||
[FieldOffset(4)] public short PreviousLevel;
|
||||
[FieldOffset(6)] public short PreviousNear;
|
||||
|
||||
#endregion
|
||||
|
||||
#region buffer
|
||||
|
||||
[FieldOffset(8)] public uint PositionMask;
|
||||
[FieldOffset(12)] public uint SizeMask;
|
||||
|
||||
#endregion
|
||||
|
||||
#region mask
|
||||
|
||||
[FieldOffset(16)] internal AllocationState State;
|
||||
[FieldOffset(17)] internal byte ReversedProfile;
|
||||
[FieldOffset(18)] internal ushort CustomizeProfile;
|
||||
[FieldOffset(20)] internal int RefCount;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0032
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct Handle : IDisposable
|
||||
{
|
||||
|
||||
[FieldOffset(8)] private readonly CacheLineMetadata* _head;
|
||||
[FieldOffset(0)] private nuint _handle;
|
||||
|
||||
private Handle(
|
||||
nuint handle,
|
||||
CacheLineMetadata* cacheRefCount
|
||||
)
|
||||
{
|
||||
_handle = handle;
|
||||
_head = cacheRefCount;
|
||||
}
|
||||
|
||||
[Obsolete("You should not create a CacheLineHandle instance outside a L4", true)]
|
||||
public Handle() { }
|
||||
|
||||
public readonly nuint Address => _handle;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_handle == 0) {
|
||||
return;
|
||||
}
|
||||
_handle = 0;
|
||||
_ = Interlocked.Decrement(ref _head->RefCount);
|
||||
}
|
||||
|
||||
}
|
||||
#pragma warning restore IDE0032
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.Debug"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
/**/
|
||||
public partial class L4
|
||||
{
|
||||
|
||||
public void DebugAgeAll()
|
||||
{
|
||||
// AgeAll();
|
||||
}
|
||||
|
||||
public bool DebugContains(
|
||||
nuint origin
|
||||
)
|
||||
{
|
||||
return _map.TryGet(origin, out _);
|
||||
}
|
||||
|
||||
public int DebugEvict(
|
||||
int count = 1
|
||||
)
|
||||
{
|
||||
return Evict(count);
|
||||
}
|
||||
|
||||
public unsafe DebugSnapshot GetDebugSnapshot()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
/*
|
||||
int maxId = _maxId;
|
||||
byte[] rrpv = new byte[maxId];
|
||||
byte* p = (byte*)_eviction;
|
||||
for (int i = 0; i < maxId; i++) {
|
||||
rrpv[i] = p[i];
|
||||
}
|
||||
return new DebugSnapshot(_activeCount, _aheadCount, maxId, rrpv);
|
||||
/**/
|
||||
}
|
||||
|
||||
public readonly struct DebugSnapshot
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// RRPV value for each line(0~3), length == MaxId
|
||||
/// </summary>
|
||||
public readonly byte[] RrpvSnapshot;
|
||||
/// <summary>
|
||||
/// Count of activated cache line handle
|
||||
/// </summary>
|
||||
public readonly int ActiveCount;
|
||||
|
||||
/// <summary>
|
||||
/// Count of predictor ahead of current map position(aheadCount)
|
||||
/// </summary>
|
||||
public readonly int AheadCount;
|
||||
|
||||
/// <summary>
|
||||
/// Max ID of the line being using or once used(maxId)
|
||||
/// </summary>
|
||||
public readonly int MaxId;
|
||||
|
||||
internal DebugSnapshot(
|
||||
int activeCount,
|
||||
int aheadCount,
|
||||
int maxId,
|
||||
byte[] rrpv
|
||||
)
|
||||
{
|
||||
ActiveCount = activeCount;
|
||||
AheadCount = aheadCount;
|
||||
MaxId = maxId;
|
||||
RrpvSnapshot = rrpv;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/**/
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.Evict"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public partial class L4
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="count">
|
||||
///
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Count of lines not evicted
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe int Evict(
|
||||
int count = 1
|
||||
)
|
||||
{
|
||||
count = (count < 0) ? 0 : count;
|
||||
int* pPtr = _predictCount, mPtr = _mapCount;
|
||||
|
||||
|
||||
int remain = count;
|
||||
for (int i = 0; i < CacheLineCount; i++)
|
||||
{
|
||||
if (pPtr[i] > mPtr[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
remain -= TryEvictAt(i) ? 1 : 0;
|
||||
}
|
||||
return remain;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe bool TryEvictAt(
|
||||
int index
|
||||
)
|
||||
{
|
||||
CacheLineMetadata* head = &_head[index];
|
||||
if (Interlocked.CompareExchange(ref head->RefCount, int.MinValue, 0) == 0)
|
||||
{
|
||||
// EvictLine(i + index);
|
||||
_ = _map.TryRemove(head->Origin, out _);
|
||||
IL4Predict predict = CurrentPredict;
|
||||
predict.WriteBack(head->Origin, head->CustomizeProfile, new ReadOnlySpan<byte>((byte*)(ulong)(head->PositionMask + _baseAddress), (int)(ulong)head->SizeMask));
|
||||
_bufferAllocator.Free(index);
|
||||
_mapCount[index] = 0;
|
||||
_predictCount[index] = int.MaxValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.Exceptions"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors
|
||||
|
||||
public sealed class L4PredictUnregisteredException : Exception
|
||||
{
|
||||
|
||||
public L4PredictUnregisteredException(
|
||||
int id
|
||||
)
|
||||
: base($"L4 predictor with id {id} is null. Maybe it has been unregistered.") { }
|
||||
|
||||
}
|
||||
|
||||
public sealed class L4PredictConflictException : Exception
|
||||
{
|
||||
|
||||
public L4PredictConflictException(
|
||||
nuint basePtr,
|
||||
ushort policy,
|
||||
int current
|
||||
)
|
||||
: base($"L4 predictor with id {current} has already registered a cache line with base pointer {basePtr} and policy {policy}.") { }
|
||||
|
||||
}
|
||||
#pragma warning restore CA1032 // Implement standard exception constructors
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.Predict"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.Collections;
|
||||
using Nitload.Common.HighPerformance.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public partial class L4
|
||||
{
|
||||
|
||||
private IL4Predict CurrentPredict { get; set; }
|
||||
|
||||
public void ResetPredict(
|
||||
IL4Predict predict
|
||||
)
|
||||
{
|
||||
if (CurrentPredict is null)
|
||||
{
|
||||
CurrentPredict = predict;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: write back all and register new predictor
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.Predictor"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
[Flags]
|
||||
public enum PredictResult
|
||||
{
|
||||
|
||||
Success = 1,
|
||||
Exhausted = 2,
|
||||
|
||||
}
|
||||
|
||||
public interface IL4Predict
|
||||
{
|
||||
|
||||
PredictResult Predict(
|
||||
Span<CacheLineDescription> node,
|
||||
out int count
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Materializes the cache line identified by <paramref name="origin" /> into <paramref ///
|
||||
/// name="cache" />.
|
||||
/// </summary>
|
||||
/// <param name="origin">
|
||||
/// Source address associated with the cache line.
|
||||
/// </param>
|
||||
/// <param name="policy">
|
||||
/// Mapping policy associated with the cache line.
|
||||
/// </param>
|
||||
/// <param name="cache">
|
||||
/// Destination cache line buffer.
|
||||
/// </param>
|
||||
void Prefetch(
|
||||
nuint origin,
|
||||
uint policy,
|
||||
Span<byte> cache
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the cache line in <paramref name="cache" /> back to <paramref name="origin" />.
|
||||
/// </summary>
|
||||
/// <param name="origin">
|
||||
/// Source address associated with the cache line.
|
||||
/// </param>
|
||||
/// <param name="policy">
|
||||
/// Mapping policy associated with the cache line.
|
||||
/// </param>
|
||||
/// <param name="cache">
|
||||
/// Source cache line buffer.
|
||||
/// </param>
|
||||
void WriteBack(
|
||||
nuint origin,
|
||||
uint policy,
|
||||
ReadOnlySpan<byte> cache
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct CacheLineDescription : IEquatable<CacheLineDescription>
|
||||
{
|
||||
|
||||
public override readonly bool Equals(
|
||||
object? obj
|
||||
)
|
||||
{
|
||||
return (obj is CacheLineDescription prediction) &&
|
||||
Address.Equals(prediction.Address) &&
|
||||
(MapPolicy == prediction.MapPolicy);
|
||||
}
|
||||
|
||||
public readonly bool Equals(
|
||||
CacheLineDescription other
|
||||
)
|
||||
{
|
||||
return
|
||||
(Address == other.Address) && (MapPolicy == other.MapPolicy);
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return (int)ComposePredictionKey64(Address, MapPolicy);
|
||||
}
|
||||
|
||||
public readonly nuint GetHashCodeLong()
|
||||
{
|
||||
return ComposePredictionKey64(Address, MapPolicy);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nuint ComposePredictionKey64(
|
||||
nuint address,
|
||||
ushort mapPolicy
|
||||
)
|
||||
{
|
||||
nuint x = address;
|
||||
nuint y = mapPolicy;
|
||||
unchecked
|
||||
{
|
||||
nuint mixed = x ^ (y * ((nuint)0x9e3779b97f4a7c15UL));
|
||||
mixed ^= mixed >> 30;
|
||||
mixed *= (nuint)0xbf58476d1ce4e5b9UL;
|
||||
mixed ^= mixed >> 27;
|
||||
mixed *= (nuint)0x94d049bb133111ebUL;
|
||||
mixed ^= mixed >> 31;
|
||||
return mixed;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator !=(
|
||||
CacheLineDescription left,
|
||||
CacheLineDescription right
|
||||
)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
public static bool operator ==(
|
||||
CacheLineDescription left,
|
||||
CacheLineDescription right
|
||||
)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1051
|
||||
[FieldOffset(0)]public nuint Address;
|
||||
[FieldOffset(8)]public uint Size;
|
||||
[FieldOffset(12)]public ushort MapPolicy;
|
||||
[FieldOffset(14)]internal short Predictor;
|
||||
#pragma warning restore CA1051
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.Prefetch"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public unsafe partial class L4
|
||||
{
|
||||
|
||||
private partial void Prefetch()
|
||||
{
|
||||
if (CurrentPredict is null) {
|
||||
return;
|
||||
}
|
||||
IL4Predict predict = CurrentPredict;
|
||||
Span<CacheLineDescription> span = new(_prediction, PredictionCount);
|
||||
PredictResult result = predict.Predict(span, out int actualCount);
|
||||
_exhausted = (result & PredictResult.Exhausted) != 0;
|
||||
if (actualCount <= 0)
|
||||
{
|
||||
_exhausted = true;
|
||||
return;
|
||||
}
|
||||
int missIndex = 0;
|
||||
for (int i = 0; i < actualCount; i++)
|
||||
{
|
||||
ref CacheLineDescription handle = ref span[i];
|
||||
nuint address = handle.Address;
|
||||
if (!_map.TryGet(address, out _))
|
||||
{
|
||||
// miss and record
|
||||
span[missIndex] = handle;
|
||||
missIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// now prefetch the misses
|
||||
for (int i = 0; i < missIndex; i++)
|
||||
{
|
||||
CacheLineMetadata* head;
|
||||
ref CacheLineDescription pHandle = ref span[i];
|
||||
nuint address = pHandle.Address;
|
||||
int index;
|
||||
if (_map.TryGet(address, out nuint handle))
|
||||
{
|
||||
head = (CacheLineMetadata*)handle;
|
||||
index = (int)(head - _head);
|
||||
_predictCount[index] = 0; // reset eviction counter
|
||||
|
||||
#region update predictor belonging
|
||||
|
||||
if (_predictCount[index] > short.MaxValue / 2)
|
||||
{
|
||||
int predictCount = Volatile.Read(ref _predictCount[index]);
|
||||
int mapCount = Volatile.Read(ref _mapCount[index]);
|
||||
int decrement = (int.Min(predictCount, mapCount) + 1) / 2;
|
||||
_ = Interlocked.Add(ref _predictCount[index], -decrement);
|
||||
_ = Interlocked.Add(ref _mapCount[index], -decrement);
|
||||
}
|
||||
_predictCount[index]++;
|
||||
|
||||
#endregion
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int retryCount = 0, remain = missIndex - i;
|
||||
const int maxRetry = 1024;
|
||||
while (!_bufferAllocator.TryAlloc(pHandle.Size, out index))
|
||||
{
|
||||
remain = Evict(remain);
|
||||
retryCount++;
|
||||
if (retryCount > maxRetry) {
|
||||
throw new OverflowException($"Failed to evict cache line after {maxRetry} retries.");
|
||||
}
|
||||
if (retryCount > maxRetry / 2) {
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
head = _head + index;
|
||||
head->Origin = address;
|
||||
head->CustomizeProfile = pHandle.MapPolicy;
|
||||
head->RefCount = 0;
|
||||
|
||||
_predictCount[index] = 1;
|
||||
_mapCount[index] = 1;
|
||||
|
||||
// pre fetch them here
|
||||
predict.Prefetch(head->Origin, head->CustomizeProfile, new Span<byte>((byte*)(head->PositionMask + _baseAddress), (int)head->SizeMask));
|
||||
_ = _map.TryAddOrSet(address, (nuint)head, out _);
|
||||
}
|
||||
_ = Interlocked.Add(ref _aheadCount, actualCount);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4.Tlsf"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.HighPerformance.ProcessorAbstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public partial class L4
|
||||
{
|
||||
|
||||
private unsafe class TLSF
|
||||
{
|
||||
|
||||
public const int MaxCacheLineCount = 8192;
|
||||
|
||||
public static readonly nuint RequestedAllocation = (nuint)MaxCacheLineCount * CacheLineMetadata.SizeOf;
|
||||
|
||||
private readonly short[] _bucket;
|
||||
private readonly CacheLineMetadata* _spareMemory;
|
||||
private readonly int _align;
|
||||
private readonly int _maxLevel;
|
||||
|
||||
private int _metadataEnd;
|
||||
private readonly Ptr64 _basePtr;
|
||||
private readonly uint _maxAllocSize;
|
||||
private readonly uint _totalSize;
|
||||
|
||||
internal TLSF(
|
||||
uint byteSize,
|
||||
uint maxBlockSize,
|
||||
void* externalPointer
|
||||
)
|
||||
{
|
||||
_totalSize = byteSize;
|
||||
_basePtr = (Ptr64)(ulong)externalPointer;
|
||||
_align = 4096; // align of a memory page
|
||||
_maxAllocSize = uint.Max(64, (~64U) & (uint.Min(maxBlockSize, CacheLineMetadata.MaxBufferSize) + 63U));
|
||||
_maxLevel = int.MaxValue;
|
||||
_maxLevel = GetLevel(_maxAllocSize);
|
||||
_bucket = new short[GetLevel(_maxAllocSize) + 2];
|
||||
_spareMemory = (CacheLineMetadata*)(ulong)(_basePtr + _totalSize);
|
||||
_metadataEnd = 0;
|
||||
|
||||
// init spare memory node
|
||||
|
||||
_spareMemory->PositionMask = 0;
|
||||
_spareMemory->SizeMask = _totalSize;
|
||||
_spareMemory->NextLevel = 0;
|
||||
_spareMemory->NextNear = 0;
|
||||
_spareMemory->PreviousLevel = 0;
|
||||
_spareMemory->PreviousNear = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetLevel(
|
||||
uint size
|
||||
)
|
||||
{
|
||||
int origin = 25 - BitOperations.LeadingZeroCount(size);
|
||||
return int.Min(origin, _maxLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put a metadata node back to free list and waiting for next allocation.
|
||||
/// </summary>
|
||||
/// <param name="node">
|
||||
///
|
||||
/// </param>
|
||||
private void RecycleNode(
|
||||
CacheLineMetadata* node
|
||||
)
|
||||
{
|
||||
short nodeIdx = (short)(_spareMemory - node);
|
||||
int level = GetLevel(node->SizeMask);
|
||||
short nextLevelIdx = node->NextLevel;
|
||||
short prevLevelIdx = node->PreviousLevel;
|
||||
RemoveFromBucket(level, node, nodeIdx);
|
||||
|
||||
// remove from position chain
|
||||
short prevNearIdx = node->PreviousNear;
|
||||
short nextNearIdx = node->NextNear;
|
||||
CacheLineMetadata* prevNear = &_spareMemory[prevNearIdx];
|
||||
prevNear->NextNear = nextNearIdx;
|
||||
if (nextNearIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* nextNear = &_spareMemory[nextNearIdx];
|
||||
nextNear->PreviousNear = prevNearIdx;
|
||||
} else
|
||||
{
|
||||
_spareMemory->PreviousNear = prevNearIdx;
|
||||
}
|
||||
node->PreviousNear = 0;
|
||||
node->NextNear = 0;
|
||||
|
||||
node->PositionMask = 0;
|
||||
node->SizeMask = 0;
|
||||
node->State = AllocationState.SpareOrCollect;
|
||||
node->PreviousLevel = 0;
|
||||
|
||||
short nextIdx = _spareMemory->NextLevel;
|
||||
if (nextIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* next = &_spareMemory[nextIdx];
|
||||
next->PreviousLevel = nodeIdx;
|
||||
}
|
||||
node->NextLevel = nextIdx;
|
||||
_spareMemory->NextLevel = nodeIdx;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void RemoveFromBucket(
|
||||
CacheLineMetadata* node,
|
||||
int nodeIdx
|
||||
)
|
||||
{
|
||||
RemoveFromBucket(GetLevel(node->SizeMask), node, nodeIdx);
|
||||
}
|
||||
|
||||
private void RemoveFromBucket(
|
||||
int level,
|
||||
CacheLineMetadata* node,
|
||||
int nodeIdx
|
||||
)
|
||||
{
|
||||
int currentLevel = level;
|
||||
short currentNextLevelIdx = node->NextLevel;
|
||||
short currentPrevLevelIdx = node->PreviousLevel;
|
||||
|
||||
if (_bucket[currentLevel] == nodeIdx)
|
||||
{
|
||||
_bucket[currentLevel] = currentNextLevelIdx;
|
||||
} else if (currentPrevLevelIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* prevLevel = &_spareMemory[currentPrevLevelIdx];
|
||||
prevLevel->NextLevel = currentNextLevelIdx;
|
||||
}
|
||||
if (currentNextLevelIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* nextLevel = &_spareMemory[currentNextLevelIdx];
|
||||
nextLevel->PreviousLevel = currentPrevLevelIdx;
|
||||
}
|
||||
node->PreviousLevel = 0;
|
||||
node->NextLevel = 0;
|
||||
}
|
||||
|
||||
// Assuming node to remove is head f bucket
|
||||
private void RemoveFromBucketHead(
|
||||
int level,
|
||||
CacheLineMetadata* node,
|
||||
int nodeIdx
|
||||
)
|
||||
{
|
||||
int currentLevel = level;
|
||||
short currentNextLevelIdx = node->NextLevel;
|
||||
|
||||
// int currentPrevLevelIdx = 0;
|
||||
|
||||
if (_bucket[currentLevel] == nodeIdx)
|
||||
{
|
||||
_bucket[currentLevel] = currentNextLevelIdx;
|
||||
}
|
||||
/*
|
||||
else if (currentPrevLevelIdx != 0)
|
||||
{
|
||||
Metadata* prevLevel = &_spareMemory[currentPrevLevelIdx];
|
||||
prevLevel->NextLevel = currentNextLevelIdx;
|
||||
}
|
||||
/**/
|
||||
if (currentNextLevelIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* nextLevel = &_spareMemory[currentNextLevelIdx];
|
||||
nextLevel->PreviousLevel = 0;
|
||||
}
|
||||
node->PreviousLevel = 0;
|
||||
node->NextLevel = 0;
|
||||
}
|
||||
|
||||
private bool TryAllocNewChunk(
|
||||
uint size,
|
||||
out CacheLineMetadata* meta
|
||||
)
|
||||
{
|
||||
short idx = _spareMemory->NextLevel;
|
||||
uint spareSize = _spareMemory->SizeMask;
|
||||
if (idx != 0)
|
||||
{
|
||||
if (spareSize < size)
|
||||
{
|
||||
meta = null;
|
||||
return false;
|
||||
}
|
||||
meta = &_spareMemory[idx];
|
||||
short nextSpareIdx = meta->NextLevel;
|
||||
if (nextSpareIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* nextSpare = &_spareMemory[nextSpareIdx];
|
||||
nextSpare->PreviousLevel = 0;
|
||||
}
|
||||
_spareMemory->NextLevel = nextSpareIdx;
|
||||
} else
|
||||
if (!TryGetNewMetadata(out meta)) {
|
||||
return false;
|
||||
}
|
||||
spareSize -= size;
|
||||
meta->SizeMask = size;
|
||||
meta->PositionMask = _spareMemory->PositionMask;
|
||||
meta->State = AllocationState.Free;
|
||||
meta->PreviousLevel = 0;
|
||||
meta->NextLevel = 0;
|
||||
_spareMemory->SizeMask = spareSize;
|
||||
_spareMemory->PositionMask += size;
|
||||
|
||||
// insert the new chunk to position chain
|
||||
short nearIdx = _spareMemory->PreviousNear;
|
||||
CacheLineMetadata* near = &_spareMemory[nearIdx];
|
||||
near->NextNear = idx;
|
||||
meta->PreviousNear = nearIdx;
|
||||
_spareMemory->PreviousNear = idx;
|
||||
meta->NextNear = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetNewMetadata(
|
||||
out CacheLineMetadata* meta
|
||||
)
|
||||
{
|
||||
ulong spareSize = _spareMemory->SizeMask;
|
||||
if (_metadataEnd >= MaxCacheLineCount)
|
||||
{
|
||||
meta = null;
|
||||
return false;
|
||||
}
|
||||
_metadataEnd++;
|
||||
meta = &_spareMemory[_metadataEnd];
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryTakeBucketEntry(
|
||||
uint minimumSize,
|
||||
out CacheLineMetadata* entry
|
||||
)
|
||||
{
|
||||
int level = GetLevel(minimumSize);
|
||||
|
||||
// first try to find in the target level
|
||||
{
|
||||
int idx = _bucket[level];
|
||||
while (idx != 0)
|
||||
{
|
||||
CacheLineMetadata* candidate = &_spareMemory[idx];
|
||||
if (candidate->SizeMask >= minimumSize)
|
||||
{
|
||||
RemoveFromBucket(level, candidate, idx);
|
||||
entry = candidate;
|
||||
return true;
|
||||
}
|
||||
idx = candidate->NextLevel;
|
||||
}
|
||||
}
|
||||
for (int i = level + 1; i < _bucket.Length; i++)
|
||||
{
|
||||
int idx = _bucket[i];
|
||||
if (idx != 0)
|
||||
{
|
||||
CacheLineMetadata* candidate = &_spareMemory[idx];
|
||||
RemoveFromBucketHead(i, candidate, idx);
|
||||
entry = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
entry = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
#region alloc and free
|
||||
|
||||
public int Alloc(
|
||||
uint size
|
||||
)
|
||||
{
|
||||
uint allocSize = uint.Max(64, (~63U) & (size + 63));
|
||||
if (allocSize > _maxAllocSize) {
|
||||
throw new OverflowException("Requested allocation size exceeds the maximum block size.");
|
||||
}
|
||||
if (!TryTakeBucketEntry(allocSize, out CacheLineMetadata* entry) &&
|
||||
!TryAllocNewChunk(allocSize, out entry)) {
|
||||
throw new OverflowException("No available ");
|
||||
}
|
||||
short entryIdx = (short)(_spareMemory - entry);
|
||||
uint entrySize = entry->SizeMask;
|
||||
if (entrySize < allocSize) {
|
||||
throw new OverflowException("Bucket returned an undersized block.");
|
||||
}
|
||||
uint remain = entrySize - allocSize;
|
||||
|
||||
// slice tail
|
||||
if (remain > ulong.Min(4096UL, entrySize >> 7) &&
|
||||
TryGetNewMetadata(out CacheLineMetadata* newEntry))
|
||||
{
|
||||
short newEntryIdx = (short)(_spareMemory - newEntry);
|
||||
newEntry->PositionMask = entry->PositionMask + allocSize;
|
||||
newEntry->SizeMask = remain;
|
||||
newEntry->State = AllocationState.Free;
|
||||
int newLevel = GetLevel(remain);
|
||||
short bucketHeadIdx = _bucket[newLevel];
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
// if there is already an entry in the bucket, link it to the new entry
|
||||
CacheLineMetadata* bucketHead = &_spareMemory[bucketHeadIdx];
|
||||
bucketHead->PreviousLevel = newEntryIdx;
|
||||
}
|
||||
newEntry->NextLevel = bucketHeadIdx;
|
||||
|
||||
newEntry->PreviousLevel = 0;
|
||||
_bucket[newLevel] = newEntryIdx;
|
||||
|
||||
// push to position chain
|
||||
short afterIdx = entry->NextNear;
|
||||
CacheLineMetadata* after = &_spareMemory[afterIdx];
|
||||
newEntry->NextNear = afterIdx;
|
||||
after->PreviousNear = newEntryIdx;
|
||||
entry->NextNear = newEntryIdx;
|
||||
newEntry->PreviousNear = entryIdx;
|
||||
}
|
||||
entry->State = AllocationState.Alloc;
|
||||
if (entry->PositionMask - (ulong)_basePtr > _totalSize) {
|
||||
throw new OverflowException("Allocated memory address exceeds the maximum block size.");
|
||||
}
|
||||
return entryIdx;
|
||||
}
|
||||
|
||||
public void Collect()
|
||||
{
|
||||
short currentIdx = _spareMemory->NextNear;
|
||||
do
|
||||
{
|
||||
if (currentIdx == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
CacheLineMetadata* current = &_spareMemory[currentIdx];
|
||||
if (current->State == AllocationState.Free)
|
||||
{
|
||||
// scan next
|
||||
uint size = 0;
|
||||
int nextIdx = current->NextNear;
|
||||
while (nextIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* next = &_spareMemory[nextIdx];
|
||||
if (next->State != AllocationState.Free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
size += next->SizeMask;
|
||||
RecycleNode(next);
|
||||
nextIdx = current->NextNear;
|
||||
}
|
||||
if (size > 0)
|
||||
{
|
||||
current->SizeMask += size;
|
||||
RemoveFromBucket(current, currentIdx);
|
||||
int newLevel = GetLevel(current->SizeMask);
|
||||
short bucketHeadIdx = _bucket[newLevel];
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* bucketHead = &_spareMemory[bucketHeadIdx];
|
||||
bucketHead->PreviousLevel = currentIdx;
|
||||
}
|
||||
current->NextLevel = bucketHeadIdx;
|
||||
current->PreviousLevel = 0;
|
||||
_bucket[newLevel] = currentIdx;
|
||||
}
|
||||
}
|
||||
currentIdx = current->NextNear;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public void Free(
|
||||
int handle,
|
||||
int collectScanWindow = 4
|
||||
)
|
||||
{
|
||||
CacheLineMetadata* head = &_spareMemory[handle];
|
||||
short headIdx = (short)(_spareMemory - head);
|
||||
uint size = head->SizeMask;
|
||||
for (int i = 0; i < collectScanWindow; i++)
|
||||
{
|
||||
// forward scan, previous direction
|
||||
int forwardIdx = head->PreviousNear;
|
||||
if (forwardIdx == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
CacheLineMetadata* prev = &_spareMemory[forwardIdx];
|
||||
if (prev->State != AllocationState.Free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
size += prev->SizeMask;
|
||||
head->PositionMask = prev->PositionMask;
|
||||
RecycleNode(prev);
|
||||
}
|
||||
for (int i = 0; i < collectScanWindow; i++)
|
||||
{
|
||||
// backward scan, next direction
|
||||
int nextIdx = head->NextNear;
|
||||
if (nextIdx == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
CacheLineMetadata* prev = &_spareMemory[nextIdx];
|
||||
if (prev->State != AllocationState.Free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
size += prev->SizeMask;
|
||||
RecycleNode(prev);
|
||||
}
|
||||
|
||||
// recycle to level;
|
||||
head->SizeMask = size;
|
||||
head->State = AllocationState.Free;
|
||||
head->PreviousLevel = 0;
|
||||
int level = GetLevel(head->SizeMask);
|
||||
short bucketHeadIdx = _bucket[level];
|
||||
head->NextLevel = 0;
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
CacheLineMetadata* bucketHead = &_spareMemory[bucketHeadIdx];
|
||||
bucketHead->PreviousLevel = headIdx;
|
||||
head->NextLevel = bucketHeadIdx;
|
||||
}
|
||||
_bucket[level] = headIdx;
|
||||
return;
|
||||
}
|
||||
|
||||
public bool TryAlloc(
|
||||
uint size,
|
||||
out int h
|
||||
)
|
||||
{
|
||||
uint allocSize = uint.Max(64, (~63U) & (size + 63));
|
||||
if (allocSize > _maxAllocSize)
|
||||
{
|
||||
h = -1;
|
||||
return false;
|
||||
}
|
||||
if (!TryTakeBucketEntry(allocSize, out CacheLineMetadata* entry) &&
|
||||
!TryAllocNewChunk(allocSize, out entry))
|
||||
{
|
||||
h = -1;
|
||||
return false;
|
||||
}
|
||||
short entryIdx = (short)(_spareMemory - entry);
|
||||
uint entrySize = entry->SizeMask;
|
||||
if (entrySize < allocSize)
|
||||
{
|
||||
h = -1;
|
||||
return false;
|
||||
}
|
||||
uint remain = entrySize - allocSize;
|
||||
|
||||
// slice tail
|
||||
if (remain > (entrySize >> 7) && TryGetNewMetadata(out CacheLineMetadata* newEntry))
|
||||
{
|
||||
short newEntryIdx = (short)(_spareMemory - newEntry);
|
||||
newEntry->PositionMask = entry->PositionMask + allocSize;
|
||||
newEntry->SizeMask = remain;
|
||||
newEntry->State = AllocationState.Free;
|
||||
int newLevel = GetLevel(remain);
|
||||
short bucketHeadIdx = _bucket[newLevel];
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
// if there is already an entry in the bucket, link it to the new entry
|
||||
CacheLineMetadata* bucketHead = &_spareMemory[bucketHeadIdx];
|
||||
newEntry->NextLevel = bucketHeadIdx;
|
||||
bucketHead->PreviousLevel = newEntryIdx;
|
||||
} else
|
||||
{
|
||||
newEntry->NextLevel = 0;
|
||||
}
|
||||
newEntry->PreviousLevel = 0;
|
||||
_bucket[newLevel] = newEntryIdx;
|
||||
|
||||
// push to position chain
|
||||
short afterIdx = entry->NextNear;
|
||||
CacheLineMetadata* after = &_spareMemory[afterIdx];
|
||||
newEntry->NextNear = afterIdx;
|
||||
after->PreviousNear = newEntryIdx;
|
||||
entry->NextNear = newEntryIdx;
|
||||
newEntry->PreviousNear = entryIdx;
|
||||
}
|
||||
entry->State = AllocationState.Alloc;
|
||||
if (entry->PositionMask - (ulong)_basePtr > _totalSize)
|
||||
{
|
||||
h = default;
|
||||
throw new OverflowException("Allocated memory address exceeds the maximum block size.");
|
||||
}
|
||||
h = entryIdx;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="L4"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.Collections;
|
||||
using Nitload.Common.HighPerformance.Memory;
|
||||
using Nitload.Common.HighPerformance.ProcessorAbstraction;
|
||||
using System.Data;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public sealed unsafe partial class L4 : IDisposable
|
||||
{
|
||||
|
||||
private const int CacheLineCount = 8192;
|
||||
private const nuint PageSize = 4096;
|
||||
private const int PredictionCount = 2048;
|
||||
private const int PredictionLeastAhead = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Array of metadata for each cache line, also used as metadata for tlsf allocator.
|
||||
/// </summary>
|
||||
private readonly CacheLineMetadata* _head;
|
||||
/// <summary>
|
||||
/// Count of cache lines mapped for each prediction, used for eviction policy.
|
||||
/// </summary>
|
||||
private readonly int* _mapCount;
|
||||
/// <summary>
|
||||
/// Count of times each cache line is predicted, used for eviction policy.
|
||||
/// </summary>
|
||||
private readonly int* _predictCount;
|
||||
/// <summary>
|
||||
/// Original memory view for prediction span.
|
||||
/// </summary>
|
||||
private readonly CacheLineDescription* _prediction;
|
||||
private bool _disposed;
|
||||
private bool _exhausted;
|
||||
|
||||
private int _aheadCount;
|
||||
|
||||
private readonly int _bindedNuma;
|
||||
private volatile int _prefetching;
|
||||
private readonly nuint _baseAddress;
|
||||
private readonly SwissTable _map;
|
||||
|
||||
private readonly TLSF _bufferAllocator;
|
||||
|
||||
public L4(
|
||||
int bindedNuma,
|
||||
uint bufferSize = 8192 * 8192,
|
||||
int align = 16,
|
||||
uint maxCacheLineSize = 8192
|
||||
)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(bufferSize, CacheLineMetadata.MaxBufferSize);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(maxCacheLineSize, CacheLineMetadata.MaxBufferSize);
|
||||
_bindedNuma = bindedNuma;
|
||||
|
||||
// init order is fallow by align requirement of each component.
|
||||
nuint headSize = ((nuint)Unsafe.SizeOf<CacheLineMetadata>()) * CacheLineCount; // 16 byte align
|
||||
nuint predictionSize = ((nuint)Unsafe.SizeOf<CacheLineDescription>()) * PredictionCount; //16 byte align
|
||||
nuint mapCountSize = SwissTable.RequestedNativeMemorySize; // 16 byte align
|
||||
nuint predictionCountSize = sizeof(int) * CacheLineCount; // 16 byte align
|
||||
nuint normalizedBufferSize = (bufferSize + (PageSize - 1)) & (~(PageSize - 1)); // align to 4096 byte, page size
|
||||
ByteCapacity = normalizedBufferSize;
|
||||
|
||||
AllocSize = headSize + mapCountSize + predictionSize + (predictionCountSize * 2) + normalizedBufferSize;
|
||||
|
||||
// malloc all memory at once, and free at once in Dispose.
|
||||
// This is to avoid fragmentation and improve performance.
|
||||
_baseAddress = (nuint)Numa.Alloc(bindedNuma, AllocSize);
|
||||
nuint ptr = _baseAddress;
|
||||
|
||||
// init all components with the buffer, and move the buffer pointer forward.
|
||||
nuint _buffer = _baseAddress;
|
||||
_bufferAllocator = new TLSF(bufferSize, maxCacheLineSize, (void*)_baseAddress);
|
||||
_buffer += normalizedBufferSize;
|
||||
_head = (CacheLineMetadata*)ptr;
|
||||
ptr += headSize;
|
||||
_prediction = (CacheLineDescription*)ptr;
|
||||
ptr += predictionSize;
|
||||
_map = SwissTable.Create(ptr, _ => { });
|
||||
ptr += mapCountSize;
|
||||
_predictCount = (int*)ptr;
|
||||
ptr += predictionCountSize;
|
||||
_mapCount = (int*)ptr;
|
||||
ptr += predictionCountSize;
|
||||
|
||||
M512* mPtr = (M512*)_head;
|
||||
for (uint i = 1; i < CacheLineCount; i++) {
|
||||
mPtr->BroadCastFrom<ulong>(0);
|
||||
}
|
||||
}
|
||||
|
||||
public nuint ByteCapacity { get; init; }
|
||||
|
||||
public nuint AllocSize { get; init; }
|
||||
|
||||
// public IL4Predict PrefetchPredict { get; set; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ContinueOnce(
|
||||
Handle line
|
||||
)
|
||||
{
|
||||
if (line.Address > 0) {
|
||||
_ = Interlocked.Decrement(ref _aheadCount);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Design",
|
||||
"CA1063:Implement IDisposable correctly",
|
||||
Justification = """
|
||||
Only owns native memory via Numa.Free();
|
||||
no managed IDisposable fields.
|
||||
Dispose(bool) pattern is unnecessary.
|
||||
""")]
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
_disposed = true;
|
||||
_map.Dispose();
|
||||
#pragma warning disable CA1816
|
||||
GC.SuppressFinalize(_bufferAllocator);
|
||||
#pragma warning restore CA1816
|
||||
|
||||
|
||||
// init order is fallow by align requirement of each component.
|
||||
Numa.Free((void*)_baseAddress, _bindedNuma, AllocSize);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Handle Map(
|
||||
ref CacheLineDescription prediction
|
||||
)
|
||||
{
|
||||
if ((_aheadCount < PredictionLeastAhead) && !_exhausted)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _prefetching, 1, 0) == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_aheadCount < PredictionLeastAhead)
|
||||
{
|
||||
// do prefetch in work load now, sync
|
||||
Prefetch();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_prefetching = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
if (_map.TryGet(prediction.GetHashCodeLong(), out nuint entryHandle))
|
||||
{
|
||||
// cache hit
|
||||
CacheLineMetadata* entry = (CacheLineMetadata*)entryHandle;
|
||||
if (Interlocked.Increment(ref entry->RefCount) > 0)
|
||||
{
|
||||
_predictCount[entry - _head] = 0;
|
||||
_ = Interlocked.Decrement(ref _aheadCount);
|
||||
_ = Interlocked.Decrement(ref _mapCount[entry - _head]);
|
||||
nuint* hPtr = stackalloc nuint[2] { entry->PositionMask + _baseAddress, (nuint)entry };
|
||||
return *(Handle*)hPtr;
|
||||
} else
|
||||
{
|
||||
_ = Interlocked.Decrement(ref entry->RefCount);
|
||||
}
|
||||
}
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
}
|
||||
|
||||
private partial void Prefetch();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="Ptr64"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
#pragma warning disable CA2225
|
||||
|
||||
public readonly struct Ptr64 : IEquatable<Ptr64>, IComparable<Ptr64>, IFormattable
|
||||
{
|
||||
|
||||
public Ptr64(
|
||||
ulong value
|
||||
)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public ulong Value { get; }
|
||||
|
||||
public int CompareTo(
|
||||
Ptr64 other
|
||||
)
|
||||
{
|
||||
return Value.CompareTo(other.Value);
|
||||
}
|
||||
|
||||
public bool Equals(
|
||||
Ptr64 other
|
||||
)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
public override bool Equals(
|
||||
object? obj
|
||||
)
|
||||
{
|
||||
return obj is Ptr64 other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
|
||||
public string ToString(
|
||||
string? format,
|
||||
IFormatProvider? formatProvider
|
||||
)
|
||||
{
|
||||
return Value.ToString(format, formatProvider);
|
||||
}
|
||||
|
||||
public static Ptr64 operator -(
|
||||
Ptr64 left,
|
||||
ulong right
|
||||
)
|
||||
{
|
||||
return new(left.Value - right);
|
||||
}
|
||||
|
||||
public static ulong operator -(
|
||||
Ptr64 left,
|
||||
Ptr64 right
|
||||
)
|
||||
{
|
||||
return left.Value - right.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(
|
||||
Ptr64 left,
|
||||
Ptr64 right
|
||||
)
|
||||
{
|
||||
return left.Value != right.Value;
|
||||
}
|
||||
|
||||
public static Ptr64 operator &(
|
||||
Ptr64 left,
|
||||
ulong mask
|
||||
)
|
||||
{
|
||||
return new(left.Value & mask);
|
||||
}
|
||||
|
||||
public static Ptr64 operator |(
|
||||
Ptr64 left,
|
||||
ulong mask
|
||||
)
|
||||
{
|
||||
return new(left.Value | mask);
|
||||
}
|
||||
|
||||
public static Ptr64 operator +(
|
||||
Ptr64 left,
|
||||
ulong right
|
||||
)
|
||||
{
|
||||
return new(left.Value + right);
|
||||
}
|
||||
|
||||
public static bool operator <(
|
||||
Ptr64 left,
|
||||
Ptr64 right
|
||||
)
|
||||
{
|
||||
return left.Value < right.Value;
|
||||
}
|
||||
|
||||
public static Ptr64 operator <<(
|
||||
Ptr64 left,
|
||||
int shift
|
||||
)
|
||||
{
|
||||
return new Ptr64(left.Value << shift);
|
||||
}
|
||||
|
||||
public static bool operator <=(
|
||||
Ptr64 left,
|
||||
Ptr64 right
|
||||
)
|
||||
{
|
||||
return left.Value <= right.Value;
|
||||
}
|
||||
|
||||
public static bool operator ==(
|
||||
Ptr64 left,
|
||||
Ptr64 right
|
||||
)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
|
||||
public static bool operator >(
|
||||
Ptr64 left,
|
||||
Ptr64 right
|
||||
)
|
||||
{
|
||||
return left.Value > right.Value;
|
||||
}
|
||||
|
||||
public static bool operator >=(
|
||||
Ptr64 left,
|
||||
Ptr64 right
|
||||
)
|
||||
{
|
||||
return left.Value >= right.Value;
|
||||
}
|
||||
|
||||
public static Ptr64 operator >>(
|
||||
Ptr64 left,
|
||||
int shift
|
||||
)
|
||||
{
|
||||
return new Ptr64(left.Value >> shift);
|
||||
}
|
||||
|
||||
public static implicit operator Ptr64(
|
||||
ulong value
|
||||
)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
public static implicit operator ulong(
|
||||
Ptr64 value
|
||||
)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public readonly struct SPtr64 : IEquatable<SPtr64>, IComparable<SPtr64>, IFormattable
|
||||
{
|
||||
|
||||
public SPtr64(
|
||||
long value
|
||||
)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public long Value { get; }
|
||||
|
||||
public int CompareTo(
|
||||
SPtr64 other
|
||||
)
|
||||
{
|
||||
return Value.CompareTo(other.Value);
|
||||
}
|
||||
|
||||
public bool Equals(
|
||||
SPtr64 other
|
||||
)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
public override bool Equals(
|
||||
object? obj
|
||||
)
|
||||
{
|
||||
return obj is SPtr64 other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
|
||||
public string ToString(
|
||||
string? format,
|
||||
IFormatProvider? formatProvider
|
||||
)
|
||||
{
|
||||
return Value.ToString(format, formatProvider);
|
||||
}
|
||||
|
||||
public static SPtr64 operator -(
|
||||
SPtr64 left,
|
||||
long right
|
||||
)
|
||||
{
|
||||
return new(left.Value - right);
|
||||
}
|
||||
|
||||
public static long operator -(
|
||||
SPtr64 left,
|
||||
SPtr64 right
|
||||
)
|
||||
{
|
||||
return left.Value - right.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(
|
||||
SPtr64 left,
|
||||
SPtr64 right
|
||||
)
|
||||
{
|
||||
return left.Value != right.Value;
|
||||
}
|
||||
|
||||
public static SPtr64 operator &(
|
||||
SPtr64 left,
|
||||
long mask
|
||||
)
|
||||
{
|
||||
return new(left.Value & mask);
|
||||
}
|
||||
|
||||
public static SPtr64 operator |(
|
||||
SPtr64 left,
|
||||
long mask
|
||||
)
|
||||
{
|
||||
return new(left.Value | mask);
|
||||
}
|
||||
|
||||
public static SPtr64 operator +(
|
||||
SPtr64 left,
|
||||
long right
|
||||
)
|
||||
{
|
||||
return new(left.Value + right);
|
||||
}
|
||||
|
||||
public static bool operator <(
|
||||
SPtr64 left,
|
||||
SPtr64 right
|
||||
)
|
||||
{
|
||||
return left.Value < right.Value;
|
||||
}
|
||||
|
||||
public static SPtr64 operator <<(
|
||||
SPtr64 left,
|
||||
int shift
|
||||
)
|
||||
{
|
||||
return new SPtr64(left.Value << shift);
|
||||
}
|
||||
|
||||
public static bool operator <=(
|
||||
SPtr64 left,
|
||||
SPtr64 right
|
||||
)
|
||||
{
|
||||
return left.Value <= right.Value;
|
||||
}
|
||||
|
||||
public static bool operator ==(
|
||||
SPtr64 left,
|
||||
SPtr64 right
|
||||
)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
|
||||
public static bool operator >(
|
||||
SPtr64 left,
|
||||
SPtr64 right
|
||||
)
|
||||
{
|
||||
return left.Value > right.Value;
|
||||
}
|
||||
|
||||
public static bool operator >=(
|
||||
SPtr64 left,
|
||||
SPtr64 right
|
||||
)
|
||||
{
|
||||
return left.Value >= right.Value;
|
||||
}
|
||||
|
||||
public static SPtr64 operator >>(
|
||||
SPtr64 left,
|
||||
int shift
|
||||
)
|
||||
{
|
||||
return new SPtr64(left.Value >> shift);
|
||||
}
|
||||
|
||||
public static implicit operator long(
|
||||
SPtr64 value
|
||||
)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public static implicit operator SPtr64(
|
||||
long value
|
||||
)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma warning disable CA2225
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SlabAllocationHandle"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public unsafe struct SlabAllocationHandle<T> : IDisposable where T : unmanaged
|
||||
{
|
||||
|
||||
private readonly SlabAllocator<T> _allocator;
|
||||
|
||||
internal SlabAllocationHandle(
|
||||
SlabAllocator<T> allocator,
|
||||
bool locked
|
||||
)
|
||||
{
|
||||
_allocator = allocator;
|
||||
Locked = locked;
|
||||
}
|
||||
|
||||
public bool Locked { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Locked)
|
||||
{
|
||||
_allocator.ExitBufferReading();
|
||||
Locked = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SlabAllocator.Chunked"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Threading;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
internal sealed unsafe class ChunkedSlabAllocator<T> : SlabAllocator<T> where T : unmanaged
|
||||
{
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private BufferStack<nuint> _buffers;
|
||||
private BufferStack<nuint> _recycle;
|
||||
private readonly int _elementSize = Unsafe.SizeOf<T>();
|
||||
|
||||
private nuint _currentBuffer;
|
||||
private nuint _currentCapacity, _currentIndex;
|
||||
|
||||
public ChunkedSlabAllocator(
|
||||
nuint count
|
||||
)
|
||||
{
|
||||
_currentBuffer = (nuint)NativeMemory.Alloc(count * ((nuint)_elementSize));
|
||||
|
||||
_currentCapacity = count;
|
||||
int initCount = count switch
|
||||
{
|
||||
<= 4 => 4,
|
||||
> 64 => 64,
|
||||
_ => (int)count
|
||||
};
|
||||
_buffers = new(4);
|
||||
_buffers.Push(_currentBuffer);
|
||||
|
||||
// Fill through a callback rather than building a scratch span and
|
||||
// passing it to BufferStackBuilder.Create: no stackalloc, no copy,
|
||||
// and no reliance on Span-to-ReadOnlySpan inference (CS0411 before
|
||||
// C# 14). Same shape as FixedSlabAllocator.
|
||||
int elementSize = _elementSize;
|
||||
_recycle = new(initCount);
|
||||
_recycle.FillRange(init, _currentBuffer);
|
||||
_currentCapacity = count;
|
||||
_currentIndex = (nuint)initCount;
|
||||
|
||||
void init(
|
||||
Span<nuint> arr,
|
||||
nuint buffer
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < arr.Length; i++) {
|
||||
arr[i] = buffer + ((nuint)(i * elementSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override nuint Allocate()
|
||||
{
|
||||
if (_recycle.TryPop(out nuint handle)) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
// nuint bufferHandle = _currentBuffer;
|
||||
nuint curIndex = _currentIndex;
|
||||
if (curIndex >= _currentCapacity)
|
||||
{
|
||||
nuint cap = _currentCapacity;
|
||||
nuint newBuffer = (nuint)NativeMemory.Alloc(cap * ((nuint)_elementSize));
|
||||
_buffers.Push(newBuffer);
|
||||
_currentBuffer = newBuffer;
|
||||
_currentIndex = 0;
|
||||
}
|
||||
curIndex = _currentIndex;
|
||||
_currentIndex += 1;
|
||||
return _currentBuffer + (((nuint)_elementSize) * curIndex);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
_disposed = true;
|
||||
|
||||
// NativeMemory.Free((void*)_currentBuffer);
|
||||
while (_buffers.TryPop(out nuint buffer))
|
||||
{
|
||||
NativeMemory.Free((void*)buffer);
|
||||
}
|
||||
_buffers.Dispose();
|
||||
_recycle.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void EnterBufferReading() { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void ExitBufferReading() { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Free(
|
||||
nuint index
|
||||
)
|
||||
{
|
||||
_recycle.Push(index);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override SlabAllocationHandle<T> ReadAllocation()
|
||||
{
|
||||
return new SlabAllocationHandle<T>(this, false);
|
||||
}
|
||||
|
||||
~ChunkedSlabAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SlabAllocator.ChunkedConcurrent"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.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 Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
internal sealed unsafe class ConcurrentChunkedSlabAllocator<T> : SlabAllocator<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
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
|
||||
};
|
||||
_buffers = new(4);
|
||||
|
||||
// Callback fill instead of scratch span + CreateConcurrent: avoids
|
||||
// the stackalloc and the copy, and sidesteps the CS0411 inference
|
||||
// gap. Safe without locking — not yet published to other threads.
|
||||
int elementSize = _elementSize;
|
||||
_recycle = new(initCount);
|
||||
_recycle.FillRange(init, _currentBuffer);
|
||||
_currentCapacity = count;
|
||||
_currentIndex = (nuint)initCount;
|
||||
|
||||
void init(
|
||||
Span<nuint> arr,
|
||||
nuint buffer
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < arr.Length; i++) {
|
||||
arr[i] = buffer + (nuint)(i * elementSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
_disposed = true;
|
||||
NativeMemory.Free((void*)_currentBuffer);
|
||||
while (_buffers.TryPop(out nuint buffer)) {
|
||||
NativeMemory.Free((void*)buffer);
|
||||
}
|
||||
_lock.Dispose();
|
||||
_buffers.Dispose();
|
||||
_recycle.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void EnterBufferReading() { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void ExitBufferReading() { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Free(
|
||||
nuint index
|
||||
)
|
||||
{
|
||||
_recycle.Push(index);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override SlabAllocationHandle<T> ReadAllocation()
|
||||
{
|
||||
return new SlabAllocationHandle<T>(this, false);
|
||||
}
|
||||
|
||||
~ConcurrentChunkedSlabAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SlabAllocator.Fixed"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.Collections;
|
||||
using Nitload.Common.HighPerformance.Memory;
|
||||
using Nitload.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
internal unsafe class FixedSlabAllocator<T> : SlabAllocator<T> where T : unmanaged
|
||||
{
|
||||
|
||||
private Action<nuint> _freeHandle;
|
||||
|
||||
private bool _disposed;
|
||||
private CapacityFixedStack<nuint> _recycle;
|
||||
private readonly int _elementSize = Unsafe.SizeOf<T>();
|
||||
|
||||
private nuint _buffer;
|
||||
|
||||
private nuint _Capacity, _currentIndex;
|
||||
|
||||
internal FixedSlabAllocator(
|
||||
nuint buffer,
|
||||
nuint count,
|
||||
Action<nuint> freeHandle
|
||||
)
|
||||
{
|
||||
_freeHandle = freeHandle;
|
||||
_buffer = buffer;
|
||||
|
||||
_Capacity = count;
|
||||
_recycle = new((int)_Capacity);
|
||||
_recycle.FillRange(init, buffer);
|
||||
_currentIndex = _Capacity;
|
||||
|
||||
static void init(
|
||||
Span<nuint> arr,
|
||||
nuint buffer
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < arr.Length; i++) {
|
||||
arr[i] = buffer + ((nuint)i * (nuint)Unsafe.SizeOf<T>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override nuint Allocate()
|
||||
{
|
||||
if (_recycle.TryPop(out nuint handle)) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
// nuint bufferHandle = _currentBuffer;
|
||||
nuint curIndex = _currentIndex;
|
||||
if (curIndex >= _Capacity) {
|
||||
return 0;
|
||||
}
|
||||
curIndex = _currentIndex;
|
||||
_currentIndex += 1;
|
||||
return _buffer + (((nuint)_elementSize) * curIndex);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
_disposed = true;
|
||||
_freeHandle(_buffer);
|
||||
_recycle = null!;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void EnterBufferReading() { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void ExitBufferReading() { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Free(
|
||||
nuint index
|
||||
)
|
||||
{
|
||||
_recycle.Push(index);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override SlabAllocationHandle<T> ReadAllocation()
|
||||
{
|
||||
return new SlabAllocationHandle<T>(this, false);
|
||||
}
|
||||
|
||||
~FixedSlabAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SlabAllocator.SequencialConcurrent"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Threading;
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public sealed unsafe class ConcurrentSequentialSlabAllocator<T> : SlabAllocator<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
|
||||
private T* _buffer;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
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
|
||||
};
|
||||
// Callback fill instead of scratch span + CreateConcurrent: avoids
|
||||
// the stackalloc and the copy, and sidesteps the CS0411 inference
|
||||
// gap. Safe without locking — not yet published to other threads.
|
||||
//
|
||||
// Note this variant stores *indices*, not addresses, so the buffer
|
||||
// argument is unused here; it is kept to match the shared
|
||||
// SpanAction<T, nuint> shape.
|
||||
_stack = new(initCount);
|
||||
_stack.FillRange(init, 0);
|
||||
_highestAllocated += (nuint)initCount;
|
||||
|
||||
static void init(
|
||||
Span<nuint> arr,
|
||||
nuint _
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < arr.Length; i++) {
|
||||
arr[i] = (nuint)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal T* BasePointer => _buffer;
|
||||
|
||||
public override nuint Allocate()
|
||||
{
|
||||
if (_stack.TryPop(out nuint index)) {
|
||||
return index;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
index = Volatile.Read(ref _highestAllocated);
|
||||
_lock.EnterMetaDataRead();
|
||||
try
|
||||
{
|
||||
if (index >= _capacity)
|
||||
{
|
||||
_lock.ExitMetaDataRead();
|
||||
_lock.EnterExpansionProcess();
|
||||
try
|
||||
{
|
||||
_capacity = Volatile.Read(ref _capacity) * 2;
|
||||
nuint newSize = ((nuint)_capacity) * ((nuint)_elementSize);
|
||||
_lock.EnterBufferCopy();
|
||||
try
|
||||
{
|
||||
_buffer = (T*)NativeMemory.Realloc(_buffer, newSize);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitBufferCopy();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitExpansionProcess();
|
||||
}
|
||||
}
|
||||
_ = Interlocked.Increment(ref Unsafe.As<nuint, ulong>(ref _highestAllocated));
|
||||
return index;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsThreadReadingMetaData) {
|
||||
_lock.ExitMetaDataRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
_disposed = true;
|
||||
_stack.Dispose();
|
||||
NativeMemory.Free(_buffer);
|
||||
_lock.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void EnterBufferReading()
|
||||
{
|
||||
_lock.EnterBufferRead();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void ExitBufferReading()
|
||||
{
|
||||
_lock.ExitBufferRead();
|
||||
}
|
||||
|
||||
public override void Free(
|
||||
nuint index
|
||||
)
|
||||
{
|
||||
_stack.Push(index);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override SlabAllocationHandle<T> ReadAllocation()
|
||||
{
|
||||
EnterBufferReading();
|
||||
return new SlabAllocationHandle<T>(this, true);
|
||||
}
|
||||
|
||||
~ConcurrentSequentialSlabAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SlabAllocator"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.HighPerformance.Memory;
|
||||
using System;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public static class SlabAllocator
|
||||
{
|
||||
|
||||
/// <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 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<T>(
|
||||
nuint count,
|
||||
SlabBufferLayout layout,
|
||||
bool concurrentSupport = true
|
||||
) where T : unmanaged
|
||||
{
|
||||
return layout switch
|
||||
{
|
||||
SlabBufferLayout.Sequential => concurrentSupport ?
|
||||
new ConcurrentSequentialSlabAllocator<T>(count) :
|
||||
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 => concurrentSupport ?
|
||||
new ConcurrentChunkedSlabAllocator<T>(count) :
|
||||
new ChunkedSlabAllocator<T>(count),
|
||||
_ => throw (new ArgumentOutOfRangeException(nameof(layout), layout, null))
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SlabAllocator<T> FromBuffer<T>(
|
||||
nuint buffer,
|
||||
nuint count,
|
||||
Action<nuint> freeHandle
|
||||
) where T : unmanaged
|
||||
{
|
||||
return new FixedSlabAllocator<T>(buffer, count, freeHandle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public abstract class SlabAllocator<T> : IDisposable where T : unmanaged
|
||||
{
|
||||
|
||||
public abstract nuint Allocate();
|
||||
|
||||
public abstract void Dispose();
|
||||
|
||||
public abstract void EnterBufferReading();
|
||||
|
||||
public abstract void ExitBufferReading();
|
||||
|
||||
public abstract void Free(
|
||||
nuint index
|
||||
);
|
||||
|
||||
public abstract SlabAllocationHandle<T> ReadAllocation();
|
||||
|
||||
}
|
||||
|
||||
public enum SlabBufferLayout
|
||||
{
|
||||
|
||||
Sequential,
|
||||
Chunked
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TwoLayerSegregatedFitAllocator"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.HighPerformance.Memory;
|
||||
using Nitload.Common.HighPerformance.ProcessorAbstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using static Nitload.Common.HighPerformance.Memory.TwoLayerSegregatedFitAllocator;
|
||||
|
||||
namespace Nitload.Common.HighPerformance.Memory
|
||||
{
|
||||
public unsafe class TwoLayerSegregatedFitAllocator : IDisposable
|
||||
{
|
||||
|
||||
private const ulong Mask48Bits = 0x0000FFFFFFFFFFFF;
|
||||
|
||||
private readonly int[] _bucket;
|
||||
private readonly Metadata* _spareMemory;
|
||||
private readonly Action<nuint> _freeHandle;
|
||||
private bool disposedValue;
|
||||
private readonly int _align;
|
||||
private readonly int _maxLevel;
|
||||
|
||||
private int _metadataEnd;
|
||||
private readonly Ptr64 _basePtr;
|
||||
private readonly ulong _maxAllocSize;
|
||||
private readonly ulong _totalSize;
|
||||
|
||||
internal TwoLayerSegregatedFitAllocator(
|
||||
nuint byteSize,
|
||||
int align,
|
||||
ulong maxBlockSize,
|
||||
Func<nuint, int, nuint> allocator,
|
||||
Action<nuint> freeHandle
|
||||
)
|
||||
: this(byteSize, align, maxBlockSize, (void*)allocator(byteSize, align), freeHandle) { }
|
||||
|
||||
internal TwoLayerSegregatedFitAllocator(
|
||||
nuint byteSize,
|
||||
int align,
|
||||
ulong maxBlockSize,
|
||||
void* externalPointer,
|
||||
Action<nuint> freeHandle
|
||||
)
|
||||
{
|
||||
_totalSize = byteSize;
|
||||
_basePtr = (Ptr64)(ulong)externalPointer;
|
||||
_align = align;
|
||||
_maxAllocSize = ulong.Max(64, (~64UL) & (ulong.Min(maxBlockSize + 63, Mask48Bits)));
|
||||
_maxLevel = int.MaxValue;
|
||||
_maxLevel = GetLevel(_maxAllocSize);
|
||||
_bucket = new int[GetLevel(_maxAllocSize) + 2];
|
||||
_spareMemory = &((Metadata*)(ulong)(_basePtr + _totalSize))[-1];
|
||||
_freeHandle = freeHandle ?? (static_ => { });
|
||||
_metadataEnd = 0;
|
||||
|
||||
// init spare memory node
|
||||
|
||||
_spareMemory->Position = _basePtr;
|
||||
_spareMemory->SizeForSpare = _totalSize - Metadata.SizeOf;
|
||||
_spareMemory->NextLevel = 0;
|
||||
_spareMemory->NextNear = 0;
|
||||
_spareMemory->PreviousLevel = 0;
|
||||
_spareMemory->PreviousNear = 0;
|
||||
}
|
||||
|
||||
public TwoLayerSegregatedFitAllocator(
|
||||
nuint byteSize
|
||||
)
|
||||
: this(byteSize, 16, 32U * 1024U * 1024U, NativeMemory.AlignedAlloc(byteSize, 16),
|
||||
ptr => NativeMemory.AlignedFree((void*)ptr)) { }
|
||||
|
||||
public TwoLayerSegregatedFitAllocator(
|
||||
nuint byteSize,
|
||||
uint maxBlockSize
|
||||
)
|
||||
: this(byteSize, 16, maxBlockSize, NativeMemory.AlignedAlloc(byteSize, 16),
|
||||
ptr => NativeMemory.AlignedFree((void*)ptr)) { }
|
||||
|
||||
public TwoLayerSegregatedFitAllocator(
|
||||
nuint byteSize,
|
||||
int align
|
||||
)
|
||||
: this(byteSize, align, 32U * 1024U * 1024U, NativeMemory.AlignedAlloc(byteSize, 16),
|
||||
ptr => NativeMemory.AlignedFree((void*)ptr)) { }
|
||||
|
||||
public TwoLayerSegregatedFitAllocator(
|
||||
nuint byteSize,
|
||||
int align,
|
||||
nuint maxBlockSize
|
||||
)
|
||||
: this(byteSize, align, maxBlockSize, NativeMemory.AlignedAlloc(byteSize, 16),
|
||||
ptr => NativeMemory.AlignedFree((void*)ptr)) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing:true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(
|
||||
bool disposing
|
||||
)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing) {
|
||||
_freeHandle((nuint)_basePtr);
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetLevel(
|
||||
ulong size
|
||||
)
|
||||
{
|
||||
int origin = 57 - BitOperations.LeadingZeroCount(size);
|
||||
return int.Min(origin, _maxLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put a metadata node back to free list and waiting for next allocation.
|
||||
/// </summary>
|
||||
/// <param name="node">
|
||||
///
|
||||
/// </param>
|
||||
private void RecycleNode(
|
||||
Metadata* node
|
||||
)
|
||||
{
|
||||
int nodeIdx = (int)(_spareMemory - node);
|
||||
int level = GetLevel(node->Size);
|
||||
int nextLevelIdx = node->NextLevel;
|
||||
int prevLevelIdx = node->PreviousLevel;
|
||||
RemoveFromBucket(level, node, nodeIdx);
|
||||
|
||||
// remove from position chain
|
||||
int prevNearIdx = node->PreviousNear;
|
||||
int nextNearIdx = node->NextNear;
|
||||
Metadata* prevNear = &_spareMemory[-prevNearIdx];
|
||||
prevNear->NextNear = nextNearIdx;
|
||||
if (nextNearIdx != 0)
|
||||
{
|
||||
Metadata* nextNear = &_spareMemory[-nextNearIdx];
|
||||
nextNear->PreviousNear = prevNearIdx;
|
||||
} else
|
||||
{
|
||||
_spareMemory->PreviousNear = prevNearIdx;
|
||||
}
|
||||
node->PreviousNear = 0;
|
||||
node->NextNear = 0;
|
||||
|
||||
node->Position = 0;
|
||||
node->SizeForSpare = 0;
|
||||
node->State = AllocationState.SpareOrCollect;
|
||||
node->PreviousLevel = 0;
|
||||
|
||||
int nextIdx = _spareMemory->NextLevel;
|
||||
if (nextIdx != 0)
|
||||
{
|
||||
Metadata* next = &_spareMemory[-nextIdx];
|
||||
next->PreviousLevel = nodeIdx;
|
||||
}
|
||||
node->NextLevel = nextIdx;
|
||||
_spareMemory->NextLevel = nodeIdx;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void RemoveFromBucket(
|
||||
Metadata* node,
|
||||
int nodeIdx
|
||||
)
|
||||
{
|
||||
RemoveFromBucket(GetLevel(node->Size), node, nodeIdx);
|
||||
}
|
||||
|
||||
private void RemoveFromBucket(
|
||||
int level,
|
||||
Metadata* node,
|
||||
int nodeIdx
|
||||
)
|
||||
{
|
||||
int currentLevel = level;
|
||||
int currentNextLevelIdx = node->NextLevel;
|
||||
int currentPrevLevelIdx = node->PreviousLevel;
|
||||
|
||||
if (_bucket[currentLevel] == nodeIdx)
|
||||
{
|
||||
_bucket[currentLevel] = currentNextLevelIdx;
|
||||
} else if (currentPrevLevelIdx != 0)
|
||||
{
|
||||
Metadata* prevLevel = &_spareMemory[-currentPrevLevelIdx];
|
||||
prevLevel->NextLevel = currentNextLevelIdx;
|
||||
}
|
||||
if (currentNextLevelIdx != 0)
|
||||
{
|
||||
Metadata* nextLevel = &_spareMemory[-currentNextLevelIdx];
|
||||
nextLevel->PreviousLevel = currentPrevLevelIdx;
|
||||
}
|
||||
node->PreviousLevel = 0;
|
||||
node->NextLevel = 0;
|
||||
}
|
||||
|
||||
// Assuming node to remove is head f bucket
|
||||
private void RemoveFromBucketHead(
|
||||
int level,
|
||||
Metadata* node,
|
||||
int nodeIdx
|
||||
)
|
||||
{
|
||||
int currentLevel = level;
|
||||
int currentNextLevelIdx = node->NextLevel;
|
||||
|
||||
// int currentPrevLevelIdx = 0;
|
||||
|
||||
if (_bucket[currentLevel] == nodeIdx)
|
||||
{
|
||||
_bucket[currentLevel] = currentNextLevelIdx;
|
||||
}
|
||||
/*
|
||||
else if (currentPrevLevelIdx != 0)
|
||||
{
|
||||
Metadata* prevLevel = &_spareMemory[-currentPrevLevelIdx];
|
||||
prevLevel->NextLevel = currentNextLevelIdx;
|
||||
}
|
||||
/**/
|
||||
if (currentNextLevelIdx != 0)
|
||||
{
|
||||
Metadata* nextLevel = &_spareMemory[-currentNextLevelIdx];
|
||||
nextLevel->PreviousLevel = 0;
|
||||
}
|
||||
node->PreviousLevel = 0;
|
||||
node->NextLevel = 0;
|
||||
}
|
||||
|
||||
private bool TryAllocNewChunk(
|
||||
ulong size,
|
||||
out Metadata* meta
|
||||
)
|
||||
{
|
||||
int idx = _spareMemory->NextLevel;
|
||||
ulong spareSize = _spareMemory->Size;
|
||||
if (idx != 0)
|
||||
{
|
||||
if (spareSize < size)
|
||||
{
|
||||
meta = null;
|
||||
return false;
|
||||
}
|
||||
meta = &_spareMemory[-idx];
|
||||
int nextSpareIdx = meta->NextLevel;
|
||||
if (nextSpareIdx != 0)
|
||||
{
|
||||
Metadata* nextSpare = &_spareMemory[-nextSpareIdx];
|
||||
nextSpare->PreviousLevel = 0;
|
||||
}
|
||||
_spareMemory->NextLevel = nextSpareIdx;
|
||||
} else
|
||||
{
|
||||
if (spareSize < Metadata.SizeOf + size)
|
||||
{
|
||||
meta = null;
|
||||
return false;
|
||||
}
|
||||
_metadataEnd++;
|
||||
idx = _metadataEnd;
|
||||
meta = &_spareMemory[-idx];
|
||||
spareSize -= Metadata.SizeOf;
|
||||
}
|
||||
spareSize -= size;
|
||||
meta->Size = size;
|
||||
meta->Position = _spareMemory->Position;
|
||||
meta->State = AllocationState.Free;
|
||||
meta->PreviousLevel = 0;
|
||||
meta->NextLevel = 0;
|
||||
_spareMemory->Size = spareSize;
|
||||
_spareMemory->Position += size;
|
||||
|
||||
// insert the new chunk to position chain
|
||||
int nearIdx = _spareMemory->PreviousNear;
|
||||
Metadata* near = &_spareMemory[-nearIdx];
|
||||
near->NextNear = idx;
|
||||
meta->PreviousNear = nearIdx;
|
||||
_spareMemory->PreviousNear = idx;
|
||||
meta->NextNear = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetNewMetadata(
|
||||
out Metadata* meta
|
||||
)
|
||||
{
|
||||
ulong spareSize = _spareMemory->Size;
|
||||
if (spareSize < Metadata.SizeOf)
|
||||
{
|
||||
meta = null;
|
||||
return false;
|
||||
}
|
||||
_metadataEnd++;
|
||||
meta = &_spareMemory[-_metadataEnd];
|
||||
spareSize -= Metadata.SizeOf;
|
||||
_spareMemory->Size = spareSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryTakeBucketEntry(
|
||||
ulong minimumSize,
|
||||
out Metadata* entry
|
||||
)
|
||||
{
|
||||
int level = GetLevel(minimumSize);
|
||||
|
||||
// first try to find in the target level
|
||||
{
|
||||
int idx = _bucket[level];
|
||||
while (idx != 0)
|
||||
{
|
||||
Metadata* candidate = &_spareMemory[-idx];
|
||||
if (candidate->Size >= minimumSize)
|
||||
{
|
||||
RemoveFromBucket(level, candidate, idx);
|
||||
entry = candidate;
|
||||
return true;
|
||||
}
|
||||
idx = candidate->NextLevel;
|
||||
}
|
||||
}
|
||||
for (int i = level + 1; i < _bucket.Length; i++)
|
||||
{
|
||||
int idx = _bucket[i];
|
||||
if (idx != 0)
|
||||
{
|
||||
Metadata* candidate = &_spareMemory[-idx];
|
||||
RemoveFromBucketHead(i, candidate, idx);
|
||||
entry = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
entry = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct Handle
|
||||
{
|
||||
|
||||
[FieldOffset(0)] internal readonly Metadata* _meta;
|
||||
[FieldOffset(8)] public readonly Ptr64 Address;
|
||||
[FieldOffset(16)] public readonly Ptr64 Size;
|
||||
|
||||
internal Handle(
|
||||
Metadata* meta,
|
||||
Ptr64 baseAddress
|
||||
)
|
||||
{
|
||||
_meta = meta;
|
||||
Address = (ulong)meta->Position;
|
||||
Size = meta->Size;
|
||||
}
|
||||
|
||||
[Obsolete("You should not create instance from default. Alloc from allocator please")]
|
||||
public Handle() { }
|
||||
|
||||
public ref byte this[
|
||||
int offset
|
||||
] => ref ((byte*)(ulong)Address)[offset];
|
||||
|
||||
public Span<byte> AsSpan(
|
||||
int range = -1
|
||||
)
|
||||
{
|
||||
return new Span<byte>((byte*)(ulong)Address, range < 0 ? (int)(ulong)Size : range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal enum AllocationState : byte
|
||||
{
|
||||
|
||||
SpareOrCollect = 0,
|
||||
Free = 1,
|
||||
Alloc = 2,
|
||||
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 32)]
|
||||
internal struct Metadata
|
||||
{
|
||||
|
||||
[FieldOffset(16)] private ulong PositionMask;
|
||||
[FieldOffset(24)] private ulong SizeMask;
|
||||
|
||||
[FieldOffset(30)] internal AllocationState State;
|
||||
[FieldOffset(31)] internal sbyte Level;
|
||||
|
||||
[FieldOffset(0)] public int NextLevel;
|
||||
[FieldOffset(4)] public int NextNear;
|
||||
[FieldOffset(8)] public int PreviousLevel;
|
||||
[FieldOffset(12)] public int PreviousNear;
|
||||
|
||||
public Ptr64 Position
|
||||
{
|
||||
readonly get => PositionMask;
|
||||
set => PositionMask = value;
|
||||
}
|
||||
|
||||
public Ptr64 Size
|
||||
{
|
||||
readonly get => (Ptr64)(SizeMask & Mask48Bits);
|
||||
set => SizeMask = (SizeMask & ~Mask48Bits) | ((ulong)value & Mask48Bits);
|
||||
}
|
||||
|
||||
public Ptr64 SizeForSpare
|
||||
{
|
||||
readonly get => SizeMask;
|
||||
set => SizeMask = value;
|
||||
}
|
||||
|
||||
public static uint SizeOf { get; } = (uint)Unsafe.SizeOf<Metadata>();
|
||||
|
||||
}
|
||||
|
||||
#region alloc and free
|
||||
|
||||
public Handle Alloc(
|
||||
nuint size
|
||||
)
|
||||
{
|
||||
ulong allocSize = ulong.Max(64, (~63UL) & ((ulong)size + 63));
|
||||
if (allocSize > _maxAllocSize) {
|
||||
throw new OverflowException("Requested allocation size exceeds the maximum block size.");
|
||||
}
|
||||
if (!TryTakeBucketEntry(allocSize, out Metadata* entry) &&
|
||||
!TryAllocNewChunk(allocSize, out entry)) {
|
||||
throw new OverflowException("No available ");
|
||||
}
|
||||
int entryIdx = (int)(_spareMemory - entry);
|
||||
ulong entrySize = entry->Size;
|
||||
if (entrySize < allocSize) {
|
||||
throw new OverflowException("Bucket returned an undersized block.");
|
||||
}
|
||||
ulong remain = entrySize - allocSize;
|
||||
|
||||
// slice tail
|
||||
if (remain > (entrySize >> 7) && TryGetNewMetadata(out Metadata* newEntry))
|
||||
{
|
||||
int newEntryIdx = (int)(_spareMemory - newEntry);
|
||||
newEntry->Position = entry->Position + allocSize;
|
||||
newEntry->Size = remain;
|
||||
newEntry->State = AllocationState.Free;
|
||||
int newLevel = GetLevel(remain);
|
||||
int bucketHeadIdx = _bucket[newLevel];
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
// if there is already an entry in the bucket, link it to the new entry
|
||||
Metadata* bucketHead = &_spareMemory[-bucketHeadIdx];
|
||||
bucketHead->PreviousLevel = newEntryIdx;
|
||||
}
|
||||
newEntry->NextLevel = bucketHeadIdx;
|
||||
|
||||
newEntry->PreviousLevel = 0;
|
||||
_bucket[newLevel] = newEntryIdx;
|
||||
|
||||
// push to position chain
|
||||
int afterIdx = entry->NextNear;
|
||||
Metadata* after = &_spareMemory[-afterIdx];
|
||||
newEntry->NextNear = afterIdx;
|
||||
after->PreviousNear = newEntryIdx;
|
||||
entry->NextNear = newEntryIdx;
|
||||
newEntry->PreviousNear = entryIdx;
|
||||
}
|
||||
entry->State = AllocationState.Alloc;
|
||||
if (entry->Position - (ulong)_basePtr > _totalSize) {
|
||||
throw new OverflowException("Allocated memory address exceeds the maximum block size.");
|
||||
}
|
||||
return new Handle(entry, _basePtr);
|
||||
}
|
||||
|
||||
public void Collect()
|
||||
{
|
||||
int currentIdx = _spareMemory->NextNear;
|
||||
do
|
||||
{
|
||||
if (currentIdx == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Metadata* current = &_spareMemory[-currentIdx];
|
||||
if (current->State == AllocationState.Free)
|
||||
{
|
||||
// scan next
|
||||
Ptr64 size = 0;
|
||||
int nextIdx = current->NextNear;
|
||||
while (nextIdx != 0)
|
||||
{
|
||||
Metadata* next = &_spareMemory[-nextIdx];
|
||||
if (next->State != AllocationState.Free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
size += next->Size;
|
||||
RecycleNode(next);
|
||||
nextIdx = current->NextNear;
|
||||
}
|
||||
if (size > 0)
|
||||
{
|
||||
current->Size += size;
|
||||
RemoveFromBucket(current, currentIdx);
|
||||
int newLevel = GetLevel(current->Size);
|
||||
int bucketHeadIdx = _bucket[newLevel];
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
Metadata* bucketHead = &_spareMemory[-bucketHeadIdx];
|
||||
bucketHead->PreviousLevel = currentIdx;
|
||||
}
|
||||
current->NextLevel = bucketHeadIdx;
|
||||
current->PreviousLevel = 0;
|
||||
_bucket[newLevel] = currentIdx;
|
||||
}
|
||||
}
|
||||
currentIdx = current->NextNear;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public void Free(
|
||||
Handle handle,
|
||||
int collectScanWindow = 4
|
||||
)
|
||||
{
|
||||
Metadata* head = handle._meta;
|
||||
int headIdx = (int)(_spareMemory - head);
|
||||
Ptr64 size = handle.Size;
|
||||
for (int i = 0; i < collectScanWindow; i++)
|
||||
{
|
||||
// forward scan, previous direction
|
||||
int forwardIdx = head->PreviousNear;
|
||||
if (forwardIdx == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Metadata* prev = &_spareMemory[-forwardIdx];
|
||||
if (prev->State != AllocationState.Free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
size += prev->Size;
|
||||
head->Position = prev->Position;
|
||||
RecycleNode(prev);
|
||||
}
|
||||
for (int i = 0; i < collectScanWindow; i++)
|
||||
{
|
||||
// backward scan, next direction
|
||||
int nextIdx = head->NextNear;
|
||||
if (nextIdx == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Metadata* prev = &_spareMemory[-nextIdx];
|
||||
if (prev->State != AllocationState.Free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
size += prev->Size;
|
||||
RecycleNode(prev);
|
||||
}
|
||||
|
||||
// recycle to level;
|
||||
head->Size = size;
|
||||
head->State = AllocationState.Free;
|
||||
head->PreviousLevel = 0;
|
||||
int level = GetLevel(head->Size);
|
||||
int bucketHeadIdx = _bucket[level];
|
||||
head->NextLevel = 0;
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
Metadata* bucketHead = &_spareMemory[-bucketHeadIdx];
|
||||
bucketHead->PreviousLevel = headIdx;
|
||||
head->NextLevel = bucketHeadIdx;
|
||||
}
|
||||
_bucket[level] = headIdx;
|
||||
return;
|
||||
}
|
||||
|
||||
public bool TryAlloc(
|
||||
nuint size,
|
||||
out Handle h
|
||||
)
|
||||
{
|
||||
ulong allocSize = ulong.Max(64, (~63UL) & ((ulong)size + 63));
|
||||
if (allocSize > _maxAllocSize)
|
||||
{
|
||||
h = default;
|
||||
return false;
|
||||
}
|
||||
if (!TryTakeBucketEntry(allocSize, out Metadata* entry) &&
|
||||
!TryAllocNewChunk(allocSize, out entry))
|
||||
{
|
||||
h = default;
|
||||
return false;
|
||||
}
|
||||
int entryIdx = (int)(_spareMemory - entry);
|
||||
ulong entrySize = entry->Size;
|
||||
if (entrySize < allocSize)
|
||||
{
|
||||
h = default;
|
||||
return false;
|
||||
}
|
||||
ulong remain = entrySize - allocSize;
|
||||
|
||||
// slice tail
|
||||
if (remain > (entrySize >> 7) && TryGetNewMetadata(out Metadata* newEntry))
|
||||
{
|
||||
int newEntryIdx = (int)(_spareMemory - newEntry);
|
||||
newEntry->Position = entry->Position + allocSize;
|
||||
newEntry->Size = remain;
|
||||
newEntry->State = AllocationState.Free;
|
||||
int newLevel = GetLevel(remain);
|
||||
int bucketHeadIdx = _bucket[newLevel];
|
||||
if (bucketHeadIdx != 0)
|
||||
{
|
||||
// if there is already an entry in the bucket, link it to the new entry
|
||||
Metadata* bucketHead = &_spareMemory[-bucketHeadIdx];
|
||||
newEntry->NextLevel = bucketHeadIdx;
|
||||
bucketHead->PreviousLevel = newEntryIdx;
|
||||
} else
|
||||
{
|
||||
newEntry->NextLevel = 0;
|
||||
}
|
||||
newEntry->PreviousLevel = 0;
|
||||
_bucket[newLevel] = newEntryIdx;
|
||||
|
||||
// push to position chain
|
||||
int afterIdx = entry->NextNear;
|
||||
Metadata* after = &_spareMemory[-afterIdx];
|
||||
newEntry->NextNear = afterIdx;
|
||||
after->PreviousNear = newEntryIdx;
|
||||
entry->NextNear = newEntryIdx;
|
||||
newEntry->PreviousNear = entryIdx;
|
||||
}
|
||||
entry->State = AllocationState.Alloc;
|
||||
if (entry->Position - (ulong)_basePtr > _totalSize)
|
||||
{
|
||||
h = default;
|
||||
throw new OverflowException("Allocated memory address exceeds the maximum block size.");
|
||||
}
|
||||
h = new Handle(entry, _basePtr);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user