hlsf wrong slicing solving

This commit is contained in:
Youmu Liu
2025-11-26 13:49:31 +08:00
parent 5b09160049
commit 23914f05ac
7 changed files with 275 additions and 142 deletions
@@ -80,26 +80,27 @@ namespace StgSharp.HighPerformance.Memory
private void SliceMemory(Entry* e, int offset) private void SliceMemory(Entry* e, int offset)
{ {
Entry* current = e; Entry* current = e;
Entry* n = e->NextNear; Entry* next = e->NextNear;
Entry* next = e;
ulong originOffset = e->Position - (ulong)m_Buffer; ulong originOffset = e->Position - (ulong)m_Buffer;
ulong finalOffset = originOffset + (ulong)offset; ulong finalOffset = originOffset + (ulong)offset;
ulong end = e->Position + e->Size; ulong end = e->Position + e->Size;
ulong endOffset = end - (ulong)m_Buffer; ulong endOffset = end - (ulong)m_Buffer;
int remainSize = (int)(e->Size - offset); ulong remainSize = (ulong)e->Size - (ulong)offset;
// get level of left edge // get level of left edge
int minLevel = int.Max((BitOperations.TrailingZeroCount(finalOffset) - 6) / 2, 0); int minLevel = int.Max((BitOperations.TrailingZeroCount(finalOffset) - 6) / 2, 0);
int i = minLevel >= levelSizeArray.Length ? levelSizeArray.Length - 1 : minLevel; int i = minLevel >= levelSizeArray.Length ? levelSizeArray.Length - 1 : minLevel;
try
{
for (; i < levelSizeArray.Length; i++) for (; i < levelSizeArray.Length; i++)
{ {
int levelSize = levelSizeArray[i]; int levelSize = levelSizeArray[i];
ulong levelLong = (ulong)levelSize; ulong levelLong = (ulong)levelSize;
ulong levelUpper = levelLong * 4; ulong levelUpper = levelLong * 4;
// smallest board that is larger than finalOffset
ulong board = (finalOffset + levelUpper - 1) / levelUpper * levelUpper; ulong board = (finalOffset + levelUpper - 1) / levelUpper * levelUpper;
int blockCount = (int)((board - ulong.Min(finalOffset, endOffset)) / levelLong); int blockCount = (int)((long)ulong.Min(board, endOffset) - (long)finalOffset) / (int)levelLong;
if (blockCount == 0) if (blockCount == 0)
{ {
continue; continue;
@@ -108,26 +109,68 @@ namespace StgSharp.HighPerformance.Memory
// create and init new entry // create and init new entry
Entry* newEntry = (Entry*)_entries.Allocate(); Entry* newEntry = (Entry*)_entries.Allocate();
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset); newEntry->State = EntryState.Empty;
newEntry->Position = (nuint)m_Buffer + (nuint)finalOffset;
newEntry->Size = (uint)s; newEntry->Size = (uint)s;
BucketNode* bucket = (BucketNode*)newEntry->Position; BucketNode* bucket = (BucketNode*)newEntry->Position;
bucket->EntryRef = newEntry; bucket->EntryRef = newEntry;
// link new entry // link new entry
newEntry->PreviousNear = current; newEntry->PreviousNear = current;
newEntry->NextNear = n; newEntry->NextNear = next;
current->NextNear = newEntry; current->NextNear = newEntry;
next->PreviousNear = newEntry; next->PreviousNear = newEntry;
newEntry->Level = i; newEntry->Level = i;
PushLevel(i, blockCount - 1, bucket); PushLevel(i, Math.Min(blockCount - 1, 2), bucket);
// set new entry // set new entry
Entry* old = current; // Entry* old = current;
current = newEntry; current = newEntry;
finalOffset += (ulong)s; finalOffset += (ulong)s;
remainSize -= s; remainSize -= (ulong)s;
if (remainSize <= (ulong)levelSize)
{
break;
}
if (remainSize <= 0) {
return;
}
}
if (i >= 10)
{
int levelSize = levelSizeArray[^1] * 4;
ulong levelLong = (ulong)levelSize;
ulong board = endOffset / levelLong * levelLong;
int blockCount = (int)((board - finalOffset) / (ulong)levelSize);
if (blockCount > 0)
{
int s = blockCount * levelSize;
Entry* newEntry = (Entry*)_entries.Allocate();
newEntry->State = EntryState.Empty;
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
newEntry->Size = (uint)s;
BucketNode* bucket = (BucketNode*)newEntry->Position;
bucket->EntryRef = newEntry;
// link new entry
newEntry->PreviousNear = current;
newEntry->NextNear = next;
current->NextNear = newEntry;
if (next != null) {
next->PreviousNear = newEntry;
}
newEntry->Level = i;
PushLevel(i, Math.Min(blockCount - 1, 2), bucket);
// update lock and data
finalOffset += (ulong)s;
remainSize -= (ulong)s;
}
if (remainSize <= 0) { if (remainSize <= 0) {
return; return;
} }
@@ -138,15 +181,17 @@ namespace StgSharp.HighPerformance.Memory
int levelSize = levelSizeArray[i]; int levelSize = levelSizeArray[i];
ulong levelLong = (ulong)levelSize; ulong levelLong = (ulong)levelSize;
ulong board = endOffset / levelLong * levelLong; ulong board = endOffset / levelLong * levelLong;
int blockCount = (int)((board - finalOffset) / (ulong)levelSize); int blockCount = (int)(board - finalOffset) / levelSize;
if (blockCount == 0) if (blockCount <= 0)
{ {
continue; continue;
} }
int s = blockCount * levelSize; int s = blockCount * levelSize;
System.Diagnostics.Debug.Assert(finalOffset + (ulong)s <= endOffset);
// create and init new entry // create and init new entry
Entry* newEntry = (Entry*)_entries.Allocate(); Entry* newEntry = (Entry*)_entries.Allocate();
newEntry->State = EntryState.Empty;
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset); newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
newEntry->Size = (uint)s; newEntry->Size = (uint)s;
BucketNode* bucket = (BucketNode*)newEntry->Position; BucketNode* bucket = (BucketNode*)newEntry->Position;
@@ -154,28 +199,34 @@ namespace StgSharp.HighPerformance.Memory
// link new entry // link new entry
newEntry->PreviousNear = current; newEntry->PreviousNear = current;
newEntry->NextNear = n; newEntry->NextNear = next;
current->NextNear = newEntry; current->NextNear = newEntry;
if (next != null) { if (next != null) {
next->PreviousNear = newEntry; next->PreviousNear = newEntry;
} }
newEntry->Level = i; newEntry->Level = i;
PushLevel(i, blockCount - 1, bucket); PushLevel(i, Math.Min(blockCount - 1, 2), bucket);
// set new entry // set new entry
Entry* old = current; // Entry* old = current;
current = newEntry; current = newEntry;
// update lock and data // update lock and data
finalOffset += (ulong)s; finalOffset += (ulong)s;
remainSize -= s; remainSize -= (ulong)s;
if (remainSize <= 0) if (remainSize <= 0)
{ {
break; break;
} }
} }
} }
finally
{
e->Size = (uint)(offset);
e->Level = GetLevelFromSize((uint)offset);
}
}
/// <summary> /// <summary>
/// Try to allocate a new 16MB block when all buckets are empty /// Try to allocate a new 16MB block when all buckets are empty
@@ -66,6 +66,7 @@ namespace StgSharp.HighPerformance.Memory
node->NextLevel = head; node->NextLevel = head;
BucketNode* headNode = head; BucketNode* headNode = head;
node->IsInBucket = true; node->IsInBucket = true;
_bucketHeads[index] = node;
// Console.WriteLine($"Push to head of level of {levelIndex} {sizeIndex}, bucket pos {(ulong)node}, previous head is at {head}"); // Console.WriteLine($"Push to head of level of {levelIndex} {sizeIndex}, bucket pos {(ulong)node}, previous head is at {head}");
if (headNode != null) if (headNode != null)
@@ -48,6 +48,7 @@ namespace StgSharp.HighPerformance.Memory
public ChunkedSlabAllocator(nuint count) public ChunkedSlabAllocator(nuint count)
{ {
_currentBuffer = (nuint)NativeMemory.Alloc(count * (nuint)_elementSize); _currentBuffer = (nuint)NativeMemory.Alloc(count * (nuint)_elementSize);
_currentCapacity = count; _currentCapacity = count;
int initCount = count switch int initCount = count switch
{ {
@@ -60,6 +61,7 @@ namespace StgSharp.HighPerformance.Memory
span[i] = _currentBuffer + (nuint)(i * _elementSize); span[i] = _currentBuffer + (nuint)(i * _elementSize);
} }
_buffers = new(4); _buffers = new(4);
_buffers.Push(_currentBuffer);
_recycle = BufferStackBuilder.Create(span); _recycle = BufferStackBuilder.Create(span);
_currentCapacity = count; _currentCapacity = count;
_currentIndex = (nuint)initCount; _currentIndex = (nuint)initCount;
@@ -70,26 +72,27 @@ namespace StgSharp.HighPerformance.Memory
if (_recycle.TryPop(out nuint handle)) { if (_recycle.TryPop(out nuint handle)) {
return handle; return handle;
} }
nuint bufferHandle = _currentBuffer;
// nuint bufferHandle = _currentBuffer;
nuint curIndex = _currentIndex; nuint curIndex = _currentIndex;
if (curIndex >= _currentCapacity) if (curIndex >= _currentCapacity)
{ {
nuint cap = _currentCapacity; nuint cap = _currentCapacity;
nuint newBuffer = (nuint)NativeMemory.Alloc(cap * (nuint)_elementSize); nuint newBuffer = (nuint)NativeMemory.Alloc(cap * (nuint)_elementSize);
_buffers.Push(bufferHandle); _buffers.Push(newBuffer);
_currentBuffer = newBuffer; _currentBuffer = newBuffer;
_currentIndex = 0; _currentIndex = 0;
} }
curIndex = _currentIndex; curIndex = _currentIndex;
_ = Interlocked.Add( _currentIndex += 1;
ref Unsafe.As<nuint, ulong>(ref _currentIndex), (ulong)_elementSize);
return _currentBuffer + ((nuint)_elementSize * curIndex); return _currentBuffer + ((nuint)_elementSize * curIndex);
} }
public override void Dispose() public override void Dispose()
{ {
NativeMemory.Free((void*)_currentBuffer); // NativeMemory.Free((void*)_currentBuffer);
while (_buffers.TryPop(out nuint buffer)) { while (_buffers.TryPop(out nuint buffer))
{
NativeMemory.Free((void*)buffer); NativeMemory.Free((void*)buffer);
} }
_buffers.Dispose(); _buffers.Dispose();
@@ -62,23 +62,26 @@ namespace StgSharp.HighPerformance.Memory
/// the /// special implementation of expandable concurrent sequential SLAB allocators, /// the /// special implementation of expandable concurrent sequential SLAB allocators,
/// they rely /// on internal expansion locks to ensure that memory buffer expansions /// 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 /// (which involve /// copying the entire buffer to a new location) do not occur while
/// other threads are /// reading from allocated regions. </para> /// other threads are /// reading from allocated regions.</para> <para><strong>
/// <para><strong>Sequential Layout Limitations: ///</strong><br /> Sequential layout SLAB /// Sequential Layout Limitations: /// </strong><br /> Sequential layout SLAB allocators
/// allocators cannot provide a single-threaded /// version because:<list /// cannot provide a single-threaded /// version because:<list
/// type="bullet"><item><description>They use contiguous memory /// layout that requires /// type="bullet"><item><description> They use contiguous memory /// layout that
/// buffer expansion when capacity is exceeded /// requires buffer expansion when capacity is exceeded ///
/// ///</description></item><item><description>Buffer expansion involves copying all /// /// </description></item><item><description> Buffer expansion involves copying all ///
/// existing data to a new memory location </description></item><item><description>/// /// existing data to a new memory location </description></item><item><description>///
/// Without proper synchronization, concurrent access to allocated objects during /// /// Without proper synchronization, concurrent access to allocated objects during ///
/// expansion would cause memory corruption </description></item><item><description> The /// expansion would cause memory corruption </description></item><item><description> The
/// /// expansion lock mechanism is essential even if allocation itself is single- /// /// /// expansion lock mechanism is essential even if allocation itself is single- ///
/// threaded </description></item></list></para> <para><strong>Chunked Layout: /// threaded</description></item></list></para> <para><strong> Chunked Layout: ///
/// ///</strong><br /> Chunked layout allocators can optionally provide single-threaded /// </strong><br /> Chunked layout allocators can optionally provide single-threaded ///
/// /// versions since they don't require buffer expansion and copying. Each chunk is /// versions since they don't require buffer expansion and copying. Each chunk is ///
/// /// allocated independently, avoiding the memory relocation issues present in /// allocated independently, avoiding the memory relocation issues present in sequential
/// sequential /// layouts. </para> /// /// layouts.</para>
/// </remarks> /// </remarks>
public static SlabAllocator<T> Create(nuint count, SlabBufferLayout layout, bool concurrentSupport = true) public static SlabAllocator<T> Create(
nuint count,
SlabBufferLayout layout,
bool concurrentSupport = true)
{ {
if (concurrentSupport) { if (concurrentSupport) {
return layout switch return layout switch
+26 -11
View File
@@ -25,10 +25,6 @@
// //
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="HlsfStressTest" (performance benchmark)
// Project: StgSharp
// -----------------------------------------------------------------------
using StgSharp.HighPerformance.Memory; using StgSharp.HighPerformance.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -46,7 +42,7 @@ namespace StgSharp
int.TryParse(mbStr, out int mb) && int.TryParse(mbStr, out int mb) &&
mb >= 64 ? mb >= 64 ?
(ulong)mb * 1024UL * 1024UL : (ulong)mb * 1024UL * 1024UL :
3UL * 1024UL * 1024UL * 1024UL; 512UL * 1024UL * 1024UL; // lower default to 512MB
private static readonly uint[] SizeSweep = { private static readonly uint[] SizeSweep = {
64, 128, 192, 256, 512, 768, 1024, 2048, 3072, 4096, 8192, 12288, 16384, 32768, 49152, 65536, 131072, 196608, 262144, 524288, 786432, 1_048_576, 2_097_152, 3_145_728, 4_194_304, 8_388_608, 12_582_912, 16_777_216, 25_165_824, 33_554_432 64, 128, 192, 256, 512, 768, 1024, 2048, 3072, 4096, 8192, 12288, 16384, 32768, 49152, 65536, 131072, 196608, 262144, 524288, 786432, 1_048_576, 2_097_152, 3_145_728, 4_194_304, 8_388_608, 12_582_912, 16_777_216, 25_165_824, 33_554_432
@@ -56,12 +52,14 @@ namespace StgSharp
{ {
Console.WriteLine("=== HLSF Allocator Single-Thread Performance Benchmark ==="); Console.WriteLine("=== HLSF Allocator Single-Thread Performance Benchmark ===");
Console.WriteLine($"Arena size: {ArenaBytes / (1024 * 1024)} MiB (env override: HLSF_ARENA_MB)"); Console.WriteLine($"Arena size: {ArenaBytes / (1024 * 1024)} MiB (env override: HLSF_ARENA_MB)");
using HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes);
Warmup(allocator);
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("[1] Allocation throughput (no touch)"); Console.WriteLine("[1] Allocation throughput (no touch)");
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
Warmup(allocator);
foreach (uint sz in SizeSweep) foreach (uint sz in SizeSweep)
{ {
uint size = sz > MaxAllocSize ? MaxAllocSize : sz; uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
@@ -69,9 +67,14 @@ namespace StgSharp
GC.Collect(); GC.Collect();
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
} }
}
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("[2] Allocation throughput (touch first byte)"); Console.WriteLine("[2] Allocation throughput (touch first byte)");
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
Warmup(allocator);
foreach (uint sz in SizeSweep) foreach (uint sz in SizeSweep)
{ {
uint size = sz > MaxAllocSize ? MaxAllocSize : sz; uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
@@ -79,19 +82,31 @@ namespace StgSharp
GC.Collect(); GC.Collect();
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
} }
}
Console.WriteLine(); Console.WriteLine();
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
Warmup(allocator);
Console.WriteLine("[3] Free throughput after pre-allocation (256k blocks of 1KB)"); Console.WriteLine("[3] Free throughput after pre-allocation (256k blocks of 1KB)");
BenchFreeThroughput(allocator, 256 * 1024, 1024); BenchFreeThroughput(allocator, 256 * 1024, 1024);
}
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("[4] Alloc+Immediate Free pairs (5,000,000 x 256B)"); Console.WriteLine("[4] Alloc+Immediate Free pairs (5,000,000 x 256B)");
BenchAllocFreePairs(allocator, 5_000_000, 256); using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
Warmup(allocator);
BenchAllocFreePairs(allocator, 5_000_000, 256);
}
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("[5] Fragmentation cycle (allocate ~1024MB random, free half, reallocate)"); Console.WriteLine("[5] Fragmentation cycle (allocate ~1024MB random, free half, reallocate)");
BenchFragmentationCycle(allocator, 1024); using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
Warmup(allocator);
BenchFragmentationCycle(allocator, 512); // target within 500MB arena
}
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("=== Benchmark Complete ==="); Console.WriteLine("=== Benchmark Complete ===");
} }
-22
View File
@@ -25,30 +25,8 @@
// //
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
using StgSharp.Collections;
using StgSharp.Controlling.UsrActivity;
using StgSharp.Geometries;
using StgSharp.Graphics;
using StgSharp.HighPerformance;
using StgSharp.HighPerformance.Memory;
using StgSharp.Internal; using StgSharp.Internal;
using StgSharp.Mathematics;
using StgSharp.Mathematics.Graphic;
using StgSharp.Mathematics.Numeric; using StgSharp.Mathematics.Numeric;
using StgSharp.Model.Step;
using StgSharp.MVVM;
using StgSharp.MVVM.ViewModel;
using StgSharp.Script;
using StgSharp.Script.Express;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Threading;
using System.Xml.XPath;
namespace StgSharp namespace StgSharp
{ {
+82
View File
@@ -0,0 +1,82 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="SlabStressTest"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using System;
using System.Diagnostics;
namespace StgSharp
{
internal static class SlabStressTest
{
// Exercise the Nonconcurrent Chunked SLAB used by HLSF (allocation/free only)
public static unsafe void Run()
{
Console.WriteLine("=== SLAB Allocator (Chunked) Single-Thread Stress Test ===");
// Use SLAB with element size of pointer (nuint) to simulate Entry handles
SlabAllocator<BunchOfBytes> slab = SlabAllocator<BunchOfBytes>.Create(64, SlabBufferLayout.Chunked, false);
int cycles = 10;
int allocsPerCycle = 200_000; // large enough to force internal growth
Stopwatch sw = new Stopwatch();
for (int c = 0; c < cycles; c++)
{
Console.WriteLine($"--- Cycle {c+1} ---");
nuint[] ptrs = new nuint[allocsPerCycle];
sw.Restart();
for (int i = 0; i < allocsPerCycle; i++) {
ptrs[i] = slab.Allocate();
}
sw.Stop();
Console.WriteLine($"Allocate: {allocsPerCycle} in {sw.Elapsed.TotalSeconds:0.00}s");
sw.Restart();
for (int i = 0; i < allocsPerCycle; i++) {
slab.Free(ptrs[i]);
}
sw.Stop();
Console.WriteLine($"Free: {allocsPerCycle} in {sw.Elapsed.TotalSeconds:0.00}s");
}
slab.Dispose();
Console.WriteLine("=== SLAB Stress Complete ===");
}
internal unsafe struct BunchOfBytes
{
public fixed byte Data[128];
}
}
}