diff --git a/StgSharp.Common/HighPerformance/Memory/L4.CacheLine.cs b/StgSharp.Common/HighPerformance/Memory/L4.CacheLine.cs
index af3edcf6..b871b479 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.CacheLine.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.CacheLine.cs
@@ -35,10 +35,18 @@ namespace StgSharp.HighPerformance.Memory
{
[InlineArray(CacheLineCount)]
- private struct EvictionArray
+ private struct PredictionArray
{
- private byte pprv;
+ private short PredictionCount;
+
+ }
+
+ [InlineArray(CacheLineCount)]
+ private struct MapArray
+ {
+
+ private short MapCount;
}
@@ -51,8 +59,8 @@ namespace StgSharp.HighPerformance.Memory
#region reference counting and state information
[FieldOffset(52)] internal int RefCount;
- [FieldOffset(56)] internal int RemainingPredictionCount;
- [FieldOffset(60)] internal int Id;
+ [FieldOffset(60)] private readonly int Reverse;
+ [FieldOffset(56)] internal int Id;
#endregion
diff --git a/StgSharp.Common/HighPerformance/Memory/L4.Evict.cs b/StgSharp.Common/HighPerformance/Memory/L4.Evict.cs
index 039f7e17..4322dbc0 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.Evict.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.Evict.cs
@@ -34,7 +34,6 @@ using System.Runtime.Intrinsics.X86;
using System.Text;
using System.Threading.Tasks;
-using Hlsf = StgSharp.HighPerformance.Memory.HybridLayerSegregatedFitAllocator;
using HlsfEntry = StgSharp.HighPerformance.Memory.HybridLayerSegregatedFitAllocator.Entry;
namespace StgSharp.HighPerformance.Memory
@@ -42,12 +41,6 @@ namespace StgSharp.HighPerformance.Memory
public partial class L4
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private unsafe void AgeAll()
- {
- _intrinsic.AgeAll(_eviction);
- }
-
///
///
///
@@ -62,41 +55,8 @@ namespace StgSharp.HighPerformance.Memory
int count = 1
)
{
- int remain = count;
- Vector128 three = Vector128.Create((byte)0x03);
- byte* p = (byte*)_eviction;
- _predictors.RequestSpanOperation();
- Span pSpan = _predictors.AsSpan();
- try
- {
- for (int i = 0; (i < CacheLineCount) && (remain > 0); i += 16)
- {
- int evictMask = _intrinsic.ScanEvict(p + i, three);
- while ((evictMask != 0) && (remain > 0))
- {
- int bitPos = BitOperations.TrailingZeroCount(evictMask);
- int index = bitPos + i;
- CacheLineHead* head = &_head[index];
- if (Interlocked.CompareExchange(ref head->RefCount, int.MinValue, 0) == 0)
- {
- // EvictLine(i + index);
- _ = _map.TryRemove(head->Origin, out _);
- int id = head->Predictor;
- IL4Predict predict = pSpan[id] ??
- throw new L4PredictUnregisteredException(id);
- predict.WriteBack(head->Origin, head->Profile, new ReadOnlySpan((byte*)head->Address, (int)head->Size));
- RecycleLine(index);
- remain--;
- }
- evictMask &= evictMask - 1;
- }
- }
- return remain;
- }
- finally
- {
- _predictors.ReleaseSpanOperation();
- }
+ count = (count < 0) ? 0 : count;
+ return _intrinsic.ScanEvict((short*)_predictCount, (short*)_mapCount, count, this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -108,6 +68,29 @@ namespace StgSharp.HighPerformance.Memory
_bufferAllocator.Free(entry);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe bool TryEvictAt(
+ int index,
+ Span predictorSpan
+ )
+ {
+ CacheLineHead* head = &_head[index];
+ if (Interlocked.CompareExchange(ref head->RefCount, int.MinValue, 0) == 0)
+ {
+ // EvictLine(i + index);
+ _ = _map.TryRemove(head->Origin, out _);
+ int id = head->Predictor;
+ IL4Predict predict = predictorSpan[id] ??
+ throw new L4PredictUnregisteredException(id);
+ predict.WriteBack(head->Origin, head->Profile, new ReadOnlySpan((byte*)head->Address, (int)head->Size));
+ RecycleLine(index);
+ (*_mapCount)[index] = 0;
+ (*_predictCount)[index] = short.MaxValue;
+ return true;
+ }
+ return false;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe bool TryGetEmptyLine(
nuint size,
@@ -120,8 +103,8 @@ namespace StgSharp.HighPerformance.Memory
index = 0;
return false;
}
- Hlsf.Entry* entry = handle.EntryHandle;
- index = (int)(entry - ((HlsfEntry*)_entryManager._basePtr));
+ CacheLineHead* entry = (CacheLineHead*)handle.EntryHandle;
+ index = entry->Id;
return true;
}
diff --git a/StgSharp.Common/HighPerformance/Memory/L4.IntrinsicKernel.cs b/StgSharp.Common/HighPerformance/Memory/L4.IntrinsicKernel.cs
index 0bef7e23..5502302b 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.IntrinsicKernel.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.IntrinsicKernel.cs
@@ -25,6 +25,7 @@
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
+using StgSharp.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -44,15 +45,12 @@ namespace StgSharp.HighPerformance.Memory
static IIntrinsicKernel Instance { get; } = Create();
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AgeAll(
- EvictionArray* eviction
- );
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ScanEvict(
- byte* vPtr,
- Vector128 three
+ short* pPtr,
+ short* mPtr,
+ int count,
+ L4 owner
);
private static IIntrinsicKernel Create()
@@ -68,35 +66,49 @@ namespace StgSharp.HighPerformance.Memory
}
- private struct SseKernel : IIntrinsicKernel
+ private readonly struct SseKernel : IIntrinsicKernel
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AgeAll(
- EvictionArray* eviction
+ public readonly int ScanEvict(
+ short* pPtr,
+ short* mPtr,
+ int count,
+ L4 owner
)
{
- Vector128 one = Vector128.Create((byte)0x01);
- Vector128 max = Vector128.Create((byte)0x03);
- byte* p = (byte*)eviction;
- for (int i = 0; i < CacheLineCount; i += 16)
+ int remain = count;
+ ConcurrentFlexibleArray predictors = owner._predictors;
+ predictors.RequestSpanOperation();
+ Span pSpan = predictors.AsSpan();
+ try
{
- Vector128 v = Sse2.LoadVector128(p + i);
- v = Sse2.AddSaturate(v, one);
- v = Sse2.Min(v, max);
- Sse2.Store(p + i, v);
- }
- }
+ for (int i = 0; i < CacheLineCount; i += 8)
+ {
+ Vector128 p = Sse2.LoadVector128(pPtr + i);
+ Vector128 m = Sse2.LoadVector128(mPtr + i);
+ Vector128 great = Sse2.CompareGreaterThan(p, m);
+ Vector128 le = Sse42.Xor(great, Vector128.Create(-1));
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int ScanEvict(
- byte* vPtr,
- Vector128 three
- )
- {
- Vector128 v = Sse2.LoadVector128(vPtr);
- Vector128 mask = Sse2.CompareEqual(v, three);
- return Sse2.MoveMask(mask);
+ int mask = Sse42.MoveMask(le.AsByte());
+ while ((mask != 0) && (remain > 0))
+ {
+ int bit = BitOperations.TrailingZeroCount(mask);
+ int lane = bit >> 1;
+
+ if (owner.TryEvictAt(i + lane, pSpan)) {
+ remain--;
+ }
+ mask &= mask - 1;
+ mask &= mask - 1;
+ }
+ }
+ }
+ finally
+ {
+ predictors.ReleaseSpanOperation();
+ }
+ return remain;
}
}
diff --git a/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs b/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs
index def8d25a..56835b88 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs
@@ -26,6 +26,7 @@
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using System.ComponentModel;
+using System.Numerics;
namespace StgSharp.HighPerformance.Memory
{
@@ -55,7 +56,6 @@ namespace StgSharp.HighPerformance.Memory
return;
}
int missIndex = 0;
- AgeAll();
for (int i = 0; i < actualCount; i++)
{
ref CacheLineDescription handle = ref span[i];
@@ -82,26 +82,40 @@ namespace StgSharp.HighPerformance.Memory
{
head = (CacheLineHead*)handle;
index = head->Id;
- (*_eviction)[index] = 0; // reset eviction counter
+ (*_predictCount)[index] = 0; // reset eviction counter
#region update predictor belonging
int predictId = head->Predictor;
IL4Predict belong = predictorSpan[predictId];
- // TODO 由于L4的缓存行改为“可变长度”缓存行,需要调整缓存行复用策略
- // This branch performs resident-line adoption rather than a simple policy
- // equality check. Returning true means the current predictor can keep using
- // the existing cache bytes without a write-back plus re-prefetch cycle.
if (!ReferenceEquals(predict, belong))
{
throw new L4PredictConflictException(head->Origin, head->Profile, predictId);
} else
{
- // Compatibility failed, so the old owner must first flush its view of
- // the line before the new predictor rematerializes the cache bytes.
-
- _ = Interlocked.Increment(ref head->RemainingPredictionCount);
+ if ((*_predictCount)[index] > short.MaxValue / 2)
+ {
+ /*
+ * EXPERIMENTAL / INTENTIONALLY UNSAFE:
+ * This code performs a 32-bit atomic operation starting from a short reference.
+ *
+ * It is only valid in this specific storage layout because:
+ * - the runtime is little-endian;
+ * - arithmetic here does not cause harmful cross-16-bit carry/borrow;
+ * - the backing storage remains valid and accessible beyond the logical short-array boundary;
+ * - touching the adjacent 16 bits is acceptable for this allocation layout;
+ * - misaligned int access is acceptable on the target platform.
+ *
+ * This is safe only with the current L4 allocation/layout guarantees.
+ */
+ short predictCount = Volatile.Read(ref (*_predictCount)[index]);
+ short mapCount = Volatile.Read(ref (*_mapCount)[index]);
+ int decrement = (short.Min(predictCount, mapCount) + 1) / 2;
+ _ = Interlocked.Add(ref Unsafe.As(ref (*_predictCount)[index]), -decrement);
+ _ = Interlocked.Add(ref Unsafe.As(ref (*_mapCount)[index]), -decrement);
+ }
+ (*_predictCount)[index]++;
}
#endregion
@@ -122,8 +136,8 @@ namespace StgSharp.HighPerformance.Memory
Thread.Sleep(1);
}
}
- head = &_head[index];
- head->_id = index;
+ head = _head + index;
+ head->Id = index;
head->Origin = address;
head->Profile = pHandle.MapPolicy;
head->RefCount = 0;
diff --git a/StgSharp.Common/HighPerformance/Memory/L4.cs b/StgSharp.Common/HighPerformance/Memory/L4.cs
index fe7ac5f7..75f9df92 100644
--- a/StgSharp.Common/HighPerformance/Memory/L4.cs
+++ b/StgSharp.Common/HighPerformance/Memory/L4.cs
@@ -44,8 +44,9 @@ namespace StgSharp.HighPerformance.Memory
private const int PredictionLeastAhead = 1024;
private static readonly IIntrinsicKernel _intrinsic = IIntrinsicKernel.Instance;
- private readonly EvictionArray* _eviction;
private readonly CacheLineHead* _head;
+ private readonly MapArray* _mapCount;
+ private readonly PredictionArray* _predictCount;
private readonly CacheLineDescription* _prediction;
private bool _disposed;
private bool _exhausted;
@@ -75,13 +76,13 @@ namespace StgSharp.HighPerformance.Memory
nuint headSize = ((nuint)Unsafe.SizeOf()) * CacheLineCount; // 16 byte align
nuint predictionSize = ((nuint)Unsafe.SizeOf()) * PredictionCount; //16 byte align
nuint mapSize = SwissTable.RequestedNativeMemorySize; // 16 byte align
- nuint rrpvSize = (nuint)Unsafe.SizeOf(); // 16 byte align
+ nuint predictionCountSize = (nuint)Unsafe.SizeOf(); // 16 byte align
nuint normalizedBufferSize = (bufferSize + (PageSize - 1)) & (~(PageSize - 1)); // align to 4096 byte, page size
ByteCapacity = 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, entrySize + headSize + mapSize + predictionSize + rrpvSize + normalizedBufferSize);
+ _baseAddress = (nuint)Numa.Alloc(bindedNuma, entrySize + headSize + mapSize + predictionSize + (predictionCountSize * 2) + normalizedBufferSize);
nuint ptr = _baseAddress;
AllocSize = entrySize;
@@ -98,8 +99,10 @@ namespace StgSharp.HighPerformance.Memory
ptr += predictionSize;
_map = SwissTable.Create(ptr, _ => { });
ptr += mapSize;
- _eviction = (EvictionArray*)ptr;
- ptr += rrpvSize;
+ _predictCount = (PredictionArray*)ptr;
+ ptr += predictionCountSize;
+ _mapCount = (MapArray*)ptr;
+ ptr += predictionCountSize;
}
public nuint ByteCapacity { get; init; }
@@ -142,9 +145,9 @@ namespace StgSharp.HighPerformance.Memory
nuint headSize = ((nuint)Unsafe.SizeOf()) * CacheLineCount; // 16 byte align
nuint predictionSize = ((nuint)Unsafe.SizeOf()) * PredictionCount; //16 byte align
nuint mapSize = SwissTable.RequestedNativeMemorySize; // 16 byte align
- nuint rrpvSize = (nuint)Unsafe.SizeOf(); // 16 byte align
+ nuint predictionCountSize = (nuint)Unsafe.SizeOf(); // 16 byte align
nuint normalizedBufferSize = ByteCapacity; // align to 4096 byte, page size
- Numa.Free((void*)_baseAddress, _bindedNuma, entrySize + headSize + mapSize + predictionSize + rrpvSize + normalizedBufferSize);
+ Numa.Free((void*)_baseAddress, _bindedNuma, entrySize + headSize + mapSize + predictionSize + (predictionCountSize * 2) + normalizedBufferSize);
GC.SuppressFinalize(this);
}
@@ -179,7 +182,7 @@ namespace StgSharp.HighPerformance.Memory
CacheLineHead* entry = (CacheLineHead*)entryHandle;
if (Interlocked.Increment(ref entry->RefCount) > 0)
{
- (*_eviction)[entry->Id] = 0;
+ (*_predictCount)[entry->Id] = 0;
_ = Interlocked.Increment(ref _activeCount);
_ = Interlocked.Decrement(ref _aheadCount);
_ = Interlocked.Decrement(ref entry->RemainingPredictionCount);