//----------------------------------------------------------------------- // ----------------------------------------------------------------------- // file="HlsfStressTest" // Project: StgSharp // AuthorGroup: Nitload Space // Copyright (c) Nitload Space. 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.Mathematics.Memory; using System; using System.Collections.Generic; using System.Diagnostics; namespace StgSharp { internal static class HlsfStressTest { private const uint MaxAllocSize = 32 * 1024 * 1024; // 32MB documented cap // Allow overriding arena size via env HLSF_ARENA_MB (must be >=64MB) private static readonly ulong ArenaBytes = Environment.GetEnvironmentVariable("HLSF_ARENA_MB") is string mbStr && int.TryParse(mbStr, out int mb) && mb >= 64 ? (ulong)mb * 1024UL * 1024UL : 3UL * 1024UL * 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 }; public static unsafe void Run() { Console.WriteLine("=== HLSF Allocator Single-Thread Performance Benchmark ==="); Console.WriteLine($"Arena size: {ArenaBytes / (1024 * 1024)} MiB (env override: HLSF_ARENA_MB)"); 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; BenchAllocThroughput(allocator, size, touch:false); allocator.Collect(); 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; BenchAllocThroughput(allocator, size, touch:true); allocator.Collect(); 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); allocator.Collect(); } Console.WriteLine(); Console.WriteLine("[4] Alloc+Immediate Free pairs (5,000,000 x 256B)"); using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes)) { Warmup(allocator); BenchAllocFreePairs(allocator, 5_000_000, 256); allocator.Collect(); } Console.WriteLine(); Console.WriteLine("[5] Fragmentation cycle (allocate ~1024MB random, free half, reallocate)"); using (HybridLayerSegregatedFitAllocator allocator = new HybridLayerSegregatedFitAllocator((nuint)ArenaBytes)) { Warmup(allocator); BenchFragmentationCycle(allocator, 512); // target within 500MB arena allocator.Collect(); } Console.WriteLine(); Console.WriteLine("=== Benchmark Complete ==="); } private static void BenchAllocFreePairs(HybridLayerSegregatedFitAllocator allocator, int pairs, uint blockSize) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < pairs; i++) { HybridLayerSegregatedFitAllocationHandle h = allocator.Alloc(blockSize); allocator.Free(h); } sw.Stop(); double opsPerSec = pairs / sw.Elapsed.TotalSeconds; // Left align and widen columns Console.WriteLine($" size={Fmt(blockSize),-12} pairs={pairs,-12} alloc+free/s={opsPerSec,-16:0.0}"); } private static void BenchAllocThroughput( HybridLayerSegregatedFitAllocator allocator, uint blockSize, bool touch) { ulong budget = ArenaBytes / 2; // use half arena for test if (blockSize == 0) { return; } long ops = (long)(budget / blockSize); ops = Math.Min(ops, blockSize >= (4 * 1024 * 1024) ? 2048 : 2_000_000); if (ops <= 0) { return; } List list = new List((int)ops); Stopwatch sw = Stopwatch.StartNew(); for (long i = 0; i < ops; i++) { HybridLayerSegregatedFitAllocationHandle h = allocator.Alloc(blockSize); if (touch) { Span span = h.BufferHandle; if (!span.IsEmpty) { span[0] = (byte)(i & 0xFF); } } list.Add(h); // Console.WriteLine(i); } sw.Stop(); double allocOpsPerSec = ops / sw.Elapsed.TotalSeconds; foreach (HybridLayerSegregatedFitAllocationHandle h in list) { allocator.Free(h); } // Left align and widen columns Console.WriteLine($" size={Fmt(blockSize),-12} ops={ops,-12} alloc/s={allocOpsPerSec,-16:0.0} touch={touch,-5}"); } private static void BenchFragmentationCycle(HybridLayerSegregatedFitAllocator allocator, int targetMiB) { int targetBytes = targetMiB * 1024 * 1024; Random rng = new Random(123); List live = new List(128 * 1024); long allocated = 0; Stopwatch swA = Stopwatch.StartNew(); while (allocated < targetBytes) { uint sz = PickSize(rng); HybridLayerSegregatedFitAllocationHandle h = allocator.Alloc(sz); live.Add(h); allocated += sz; } swA.Stop(); Console.WriteLine($" phaseA alloc {targetMiB}MB in {swA.Elapsed.TotalSeconds:0.00}s blocks={live.Count}"); Stopwatch swB = Stopwatch.StartNew(); int freed = 0; for (int i = live.Count - 1; i >= 0; i -= 2) { allocator.Free(live[i]); live.RemoveAt(i); freed++; } swB.Stop(); Console.WriteLine($" phaseB freed {freed} in {swB.Elapsed.TotalSeconds:0.00}s remain={live.Count}"); allocated = 0; int reCount = 0; Stopwatch swC = Stopwatch.StartNew(); while (allocated < targetBytes / 2) { uint sz = PickSize(rng); HybridLayerSegregatedFitAllocationHandle h = allocator.Alloc(sz); live.Add(h); allocated += sz; reCount++; } swC.Stop(); Console.WriteLine($" phaseC re-alloc {reCount} in {swC.Elapsed.TotalSeconds:0.00}s ~{allocated / (1024.0 * 1024.0):0}MB"); Stopwatch swD = Stopwatch.StartNew(); foreach (HybridLayerSegregatedFitAllocationHandle h in live) { allocator.Free(h); } swD.Stop(); Console.WriteLine($" cleanup freed {live.Count} in {swD.Elapsed.TotalSeconds:0.00}s"); } private static void BenchFreeThroughput(HybridLayerSegregatedFitAllocator allocator, int count, uint blockSize) { List list = new List(count); for (int i = 0; i < count; i++) { list.Add(allocator.Alloc(blockSize)); } Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { allocator.Free(list[i]); } sw.Stop(); double freesPerSec = count / sw.Elapsed.TotalSeconds; // Left align and widen columns Console.WriteLine($" size={Fmt(blockSize),-12} count={count,-12} free/s={freesPerSec,-16:0.0}"); } private static string Fmt(uint bytes) { if (bytes >= 1024 * 1024) { return $"{bytes / (1024 * 1024)}MB"; } if (bytes >= 1024) { return $"{bytes / 1024}KB"; } return $"{bytes}B"; } private static uint PickSize(Random rng) { uint raw = SizeSweep[rng.Next(SizeSweep.Length)]; return raw > MaxAllocSize ? MaxAllocSize : raw; } private static void Warmup(HybridLayerSegregatedFitAllocator allocator) { List tmp = new List(4096); for (int i = 0; i < 4096; i++) { tmp.Add(allocator.Alloc(256)); } foreach (HybridLayerSegregatedFitAllocationHandle h in tmp) { allocator.Free(h); } } } }