hlsf wrong slicing solving
This commit is contained in:
@@ -80,26 +80,27 @@ 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;
|
||||
|
||||
try
|
||||
{
|
||||
for (; i < levelSizeArray.Length; i++)
|
||||
{
|
||||
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)((board - ulong.Min(finalOffset, endOffset)) / levelLong);
|
||||
int blockCount = (int)((long)ulong.Min(board, endOffset) - (long)finalOffset) / (int)levelLong;
|
||||
if (blockCount == 0)
|
||||
{
|
||||
continue;
|
||||
@@ -108,26 +109,68 @@ namespace StgSharp.HighPerformance.Memory
|
||||
|
||||
// create and init new entry
|
||||
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;
|
||||
BucketNode* bucket = (BucketNode*)newEntry->Position;
|
||||
bucket->EntryRef = newEntry;
|
||||
|
||||
// link new entry
|
||||
newEntry->PreviousNear = current;
|
||||
newEntry->NextNear = n;
|
||||
newEntry->NextNear = next;
|
||||
current->NextNear = newEntry;
|
||||
next->PreviousNear = newEntry;
|
||||
|
||||
newEntry->Level = i;
|
||||
PushLevel(i, blockCount - 1, bucket);
|
||||
PushLevel(i, Math.Min(blockCount - 1, 2), bucket);
|
||||
|
||||
// set new entry
|
||||
Entry* old = current;
|
||||
// Entry* old = current;
|
||||
current = newEntry;
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -138,15 +181,17 @@ namespace StgSharp.HighPerformance.Memory
|
||||
int levelSize = levelSizeArray[i];
|
||||
ulong levelLong = (ulong)levelSize;
|
||||
ulong board = endOffset / levelLong * levelLong;
|
||||
int blockCount = (int)((board - finalOffset) / (ulong)levelSize);
|
||||
if (blockCount == 0)
|
||||
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;
|
||||
@@ -154,28 +199,34 @@ namespace StgSharp.HighPerformance.Memory
|
||||
|
||||
// link new entry
|
||||
newEntry->PreviousNear = current;
|
||||
newEntry->NextNear = n;
|
||||
newEntry->NextNear = next;
|
||||
current->NextNear = newEntry;
|
||||
if (next != null) {
|
||||
next->PreviousNear = newEntry;
|
||||
}
|
||||
|
||||
newEntry->Level = i;
|
||||
PushLevel(i, blockCount - 1, bucket);
|
||||
PushLevel(i, Math.Min(blockCount - 1, 2), bucket);
|
||||
|
||||
// set new entry
|
||||
Entry* old = current;
|
||||
// Entry* old = current;
|
||||
current = newEntry;
|
||||
|
||||
// update lock and data
|
||||
finalOffset += (ulong)s;
|
||||
remainSize -= s;
|
||||
remainSize -= (ulong)s;
|
||||
if (remainSize <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
e->Size = (uint)(offset);
|
||||
e->Level = GetLevelFromSize((uint)offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to allocate a new 16MB block when all buckets are empty
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,12 +52,14 @@ 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)");
|
||||
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
|
||||
{
|
||||
Warmup(allocator);
|
||||
|
||||
foreach (uint sz in SizeSweep)
|
||||
{
|
||||
uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
|
||||
@@ -69,9 +67,14 @@ namespace StgSharp
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("[2] Allocation throughput (touch first byte)");
|
||||
using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes))
|
||||
{
|
||||
Warmup(allocator);
|
||||
|
||||
foreach (uint sz in SizeSweep)
|
||||
{
|
||||
uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
|
||||
@@ -79,19 +82,31 @@ namespace StgSharp
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
|
||||
}
|
||||
Console.WriteLine();
|
||||
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 ===");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user