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,100 +80,151 @@ namespace StgSharp.HighPerformance.Memory
private void SliceMemory(Entry* e, int offset)
{
Entry* current = e;
Entry* n = e->NextNear;
Entry* next = e;
Entry* next = e->NextNear;
ulong originOffset = e->Position - (ulong)m_Buffer;
ulong finalOffset = originOffset + (ulong)offset;
ulong end = e->Position + e->Size;
ulong endOffset = end - (ulong)m_Buffer;
int remainSize = (int)(e->Size - offset);
ulong remainSize = (ulong)e->Size - (ulong)offset;
// get level of left edge
int minLevel = int.Max((BitOperations.TrailingZeroCount(finalOffset) - 6) / 2, 0);
int i = minLevel >= levelSizeArray.Length ? levelSizeArray.Length - 1 : minLevel;
for (; i < levelSizeArray.Length; i++)
try
{
int levelSize = levelSizeArray[i];
ulong levelLong = (ulong)levelSize;
ulong levelUpper = levelLong * 4;
ulong board = (finalOffset + levelUpper - 1) / levelUpper * levelUpper;
int blockCount = (int)((board - ulong.Min(finalOffset, endOffset)) / levelLong);
if (blockCount == 0)
for (; i < levelSizeArray.Length; i++)
{
continue;
int levelSize = levelSizeArray[i];
ulong levelLong = (ulong)levelSize;
ulong levelUpper = levelLong * 4;
// smallest board that is larger than finalOffset
ulong board = (finalOffset + levelUpper - 1) / levelUpper * levelUpper;
int blockCount = (int)((long)ulong.Min(board, endOffset) - (long)finalOffset) / (int)levelLong;
if (blockCount == 0)
{
continue;
}
int s = blockCount * levelSize;
// create and init new entry
Entry* newEntry = (Entry*)_entries.Allocate();
newEntry->State = EntryState.Empty;
newEntry->Position = (nuint)m_Buffer + (nuint)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;
next->PreviousNear = newEntry;
newEntry->Level = i;
PushLevel(i, Math.Min(blockCount - 1, 2), bucket);
// set new entry
// Entry* old = current;
current = newEntry;
finalOffset += (ulong)s;
remainSize -= (ulong)s;
if (remainSize <= (ulong)levelSize)
{
break;
}
if (remainSize <= 0) {
return;
}
}
int s = blockCount * levelSize;
// create and init new entry
Entry* newEntry = (Entry*)_entries.Allocate();
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
newEntry->Size = (uint)s;
BucketNode* bucket = (BucketNode*)newEntry->Position;
bucket->EntryRef = newEntry;
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 = n;
current->NextNear = newEntry;
next->PreviousNear = newEntry;
newEntry->Level = i;
PushLevel(i, blockCount - 1, bucket);
// link new entry
newEntry->PreviousNear = current;
newEntry->NextNear = next;
current->NextNear = newEntry;
if (next != null) {
next->PreviousNear = newEntry;
}
// set new entry
Entry* old = current;
current = newEntry;
newEntry->Level = i;
PushLevel(i, Math.Min(blockCount - 1, 2), bucket);
finalOffset += (ulong)s;
remainSize -= s;
if (remainSize <= 0) {
return;
// update lock and data
finalOffset += (ulong)s;
remainSize -= (ulong)s;
}
if (remainSize <= 0) {
return;
}
}
for (i -= 1; i >= 0; i--)
{
int levelSize = levelSizeArray[i];
ulong levelLong = (ulong)levelSize;
ulong board = endOffset / levelLong * levelLong;
int blockCount = (int)(board - finalOffset) / levelSize;
if (blockCount <= 0)
{
continue;
}
int s = blockCount * levelSize;
System.Diagnostics.Debug.Assert(finalOffset + (ulong)s <= endOffset);
// create and init new entry
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);
// set new entry
// Entry* old = current;
current = newEntry;
// update lock and data
finalOffset += (ulong)s;
remainSize -= (ulong)s;
if (remainSize <= 0)
{
break;
}
}
}
for (i -= 1; i >= 0; i--)
finally
{
int levelSize = levelSizeArray[i];
ulong levelLong = (ulong)levelSize;
ulong board = endOffset / levelLong * levelLong;
int blockCount = (int)((board - finalOffset) / (ulong)levelSize);
if (blockCount == 0)
{
continue;
}
int s = blockCount * levelSize;
// create and init new entry
Entry* newEntry = (Entry*)_entries.Allocate();
newEntry->Position = (nuint)((ulong)m_Buffer + finalOffset);
newEntry->Size = (uint)s;
BucketNode* bucket = (BucketNode*)newEntry->Position;
bucket->EntryRef = newEntry;
// link new entry
newEntry->PreviousNear = current;
newEntry->NextNear = n;
current->NextNear = newEntry;
if (next != null) {
next->PreviousNear = newEntry;
}
newEntry->Level = i;
PushLevel(i, blockCount - 1, bucket);
// set new entry
Entry* old = current;
current = newEntry;
// update lock and data
finalOffset += (ulong)s;
remainSize -= s;
if (remainSize <= 0)
{
break;
}
e->Size = (uint)(offset);
e->Level = GetLevelFromSize((uint)offset);
}
}
@@ -66,6 +66,7 @@ namespace StgSharp.HighPerformance.Memory
node->NextLevel = head;
BucketNode* headNode = head;
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}");
if (headNode != null)
@@ -35,7 +35,7 @@ using System.Threading;
namespace StgSharp.HighPerformance.Memory
{
internal sealed unsafe class ChunkedSlabAllocator<T> : SlabAllocator<T> where T: unmanaged
internal sealed unsafe class ChunkedSlabAllocator<T> : SlabAllocator<T> where T : unmanaged
{
private BufferStack<nuint> _buffers;
@@ -48,6 +48,7 @@ namespace StgSharp.HighPerformance.Memory
public ChunkedSlabAllocator(nuint count)
{
_currentBuffer = (nuint)NativeMemory.Alloc(count * (nuint)_elementSize);
_currentCapacity = count;
int initCount = count switch
{
@@ -60,6 +61,7 @@ namespace StgSharp.HighPerformance.Memory
span[i] = _currentBuffer + (nuint)(i * _elementSize);
}
_buffers = new(4);
_buffers.Push(_currentBuffer);
_recycle = BufferStackBuilder.Create(span);
_currentCapacity = count;
_currentIndex = (nuint)initCount;
@@ -70,26 +72,27 @@ namespace StgSharp.HighPerformance.Memory
if (_recycle.TryPop(out nuint handle)) {
return handle;
}
nuint bufferHandle = _currentBuffer;
// nuint bufferHandle = _currentBuffer;
nuint curIndex = _currentIndex;
if (curIndex >= _currentCapacity)
{
nuint cap = _currentCapacity;
nuint newBuffer = (nuint)NativeMemory.Alloc(cap * (nuint)_elementSize);
_buffers.Push(bufferHandle);
_buffers.Push(newBuffer);
_currentBuffer = newBuffer;
_currentIndex = 0;
}
curIndex = _currentIndex;
_ = Interlocked.Add(
ref Unsafe.As<nuint, ulong>(ref _currentIndex), (ulong)_elementSize);
_currentIndex += 1;
return _currentBuffer + ((nuint)_elementSize * curIndex);
}
public override void Dispose()
{
NativeMemory.Free((void*)_currentBuffer);
while (_buffers.TryPop(out nuint buffer)) {
// NativeMemory.Free((void*)_currentBuffer);
while (_buffers.TryPop(out nuint buffer))
{
NativeMemory.Free((void*)buffer);
}
_buffers.Dispose();
@@ -29,7 +29,7 @@ using System;
namespace StgSharp.HighPerformance.Memory
{
public abstract class SlabAllocator<T> : IDisposable where T: unmanaged
public abstract class SlabAllocator<T> : IDisposable where T : unmanaged
{
public abstract nuint Allocate();
@@ -56,29 +56,32 @@ namespace StgSharp.HighPerformance.Memory
/// Thrown when requesting unsupported configurations.
/// </exception>
/// <remarks>
/// <para><strong> Important Threading Considerations: </strong></para> <para>While SLAB
/// <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> ///
/// other threads are /// reading from allocated regions.</para> <para><strong>
/// Sequential Layout Limitations: /// </strong><br /> Sequential layout SLAB allocators
/// cannot provide a single-threaded /// version because:<list
/// type="bullet"><item><description> They use contiguous memory /// layout that
/// requires buffer expansion when capacity is exceeded ///
/// </description></item><item><description> Buffer expansion involves copying all ///
/// existing data to a new memory location </description></item><item><description>///
/// Without proper synchronization, concurrent access to allocated objects during ///
/// expansion would cause memory corruption</description></item><item><description>The
/// expansion 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>
/// threaded</description></item></list></para> <para><strong> Chunked Layout: ///
/// </strong><br /> Chunked layout allocators can optionally provide single-threaded ///
/// versions since they don't require buffer expansion and copying. Each chunk is ///
/// allocated independently, avoiding the memory relocation issues present in sequential
/// /// layouts.</para>
/// </remarks>
public static SlabAllocator<T> Create(nuint count, SlabBufferLayout layout, bool concurrentSupport = true)
public static SlabAllocator<T> Create(
nuint count,
SlabBufferLayout layout,
bool concurrentSupport = true)
{
if (concurrentSupport) {
return layout switch
+37 -22
View File
@@ -25,10 +25,6 @@
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="HlsfStressTest" (performance benchmark)
// Project: StgSharp
// -----------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using System;
using System.Collections.Generic;
@@ -46,7 +42,7 @@ namespace StgSharp
int.TryParse(mbStr, out int mb) &&
mb >= 64 ?
(ulong)mb * 1024UL * 1024UL :
3UL * 1024UL * 1024UL * 1024UL;
512UL * 1024UL * 1024UL; // lower default to 512MB
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
@@ -56,42 +52,61 @@ namespace StgSharp
{
Console.WriteLine("=== HLSF Allocator Single-Thread Performance Benchmark ===");
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("[1] Allocation throughput (no touch)");
foreach (uint sz in SizeSweep)
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
BenchAllocThroughput(allocator, size, touch:false);
GC.Collect();
GC.WaitForPendingFinalizers();
Warmup(allocator);
foreach (uint sz in SizeSweep)
{
uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
BenchAllocThroughput(allocator, size, touch:false);
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Console.WriteLine();
Console.WriteLine("[2] Allocation throughput (touch first byte)");
foreach (uint sz in SizeSweep)
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
BenchAllocThroughput(allocator, size, touch:true);
GC.Collect();
GC.WaitForPendingFinalizers();
Warmup(allocator);
foreach (uint sz in SizeSweep)
{
uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
BenchAllocThroughput(allocator, size, touch:true);
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Console.WriteLine();
Console.WriteLine("[3] Free throughput after pre-allocation (256k blocks of 1KB)");
BenchFreeThroughput(allocator, 256 * 1024, 1024);
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
{
Warmup(allocator);
Console.WriteLine("[3] Free throughput after pre-allocation (256k blocks of 1KB)");
BenchFreeThroughput(allocator, 256 * 1024, 1024);
}
Console.WriteLine();
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("[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("=== 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.Mathematics;
using StgSharp.Mathematics.Graphic;
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
{
+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];
}
}
}