From 29bee9935585af20ceeaee99ea3cd1dc072f4d3e Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:30:22 +0800 Subject: [PATCH] add a benchmark proj --- CMakeLists.txt | 61 -------- ReadMe.md | 10 ++ StgSharp.Benchmark/HlsfAllocatorBench.cs | 138 ++++++++++++++++++ StgSharp.Benchmark/Program.cs | 44 ++++++ StgSharp.Benchmark/StgSharp.Benchmark.csproj | 32 ++++ .../Memory/InroductionToHLSF.md | 27 +++- StgSharp.Common/StgSharp.Common.csproj | 11 +- .../RegularExpression.StateMachine.cs | 9 ++ StgSharpDevelop.sln | 15 ++ 9 files changed, 274 insertions(+), 73 deletions(-) delete mode 100644 CMakeLists.txt create mode 100644 StgSharp.Benchmark/HlsfAllocatorBench.cs create mode 100644 StgSharp.Benchmark/Program.cs create mode 100644 StgSharp.Benchmark/StgSharp.Benchmark.csproj diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 0132e8ff..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,61 +0,0 @@ -cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) - -set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE) - -project(StgSharpDevelop C) - -################################################################################ -# Compiler and environment checks -################################################################################ - -# Detect compiler family and allow only GCC or Clang when using CMake. -if(MSVC) - message(FATAL_ERROR "MSVC detected while configuring with CMake. This repository provides .vcxproj files for MSVC/Visual Studio.\nPlease open the solution in Visual Studio or use the existing .vcxproj files to build with MSVC. CMake-based MSVC builds are not supported by this project.") -endif() - -if(CMAKE_C_COMPILER_ID STREQUAL "GNU") - message(STATUS "Using GCC: ${CMAKE_C_COMPILER}") -elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang") - message(STATUS "Using Clang: ${CMAKE_C_COMPILER}") -else() - message(FATAL_ERROR "Unsupported compiler '${CMAKE_C_COMPILER_ID}'. Supported compilers for CMake builds are: GCC and Clang. MSVC users: use the provided .vcxproj and Visual Studio.") -endif() - -################################################################################ -# Global configuration types -################################################################################ -set(CMAKE_CONFIGURATION_TYPES - "Debug" - "Release" - CACHE STRING "" FORCE -) - -################################################################################ -# Nuget packages function stub. -################################################################################ -function(use_package TARGET PACKAGE VERSION) - message(WARNING "No implementation of use_package. Create yours. " - "Package \"${PACKAGE}\" with version \"${VERSION}\" " - "for target \"${TARGET}\" is ignored!") -endfunction() - -################################################################################ -# Common utils -################################################################################ -include(CMake/Utils.cmake) - -################################################################################ -# Additional Global Settings(add specific info there) -################################################################################ -include(CMake/GlobalSettingsInclude.cmake OPTIONAL) - -################################################################################ -# Use solution folders feature -################################################################################ -set_property(GLOBAL PROPERTY USE_FOLDERS ON) - -################################################################################ -# Sub-projects -################################################################################ -add_subdirectory(StgSharp.Native) - diff --git a/ReadMe.md b/ReadMe.md index ce827fa4..233f6a3a 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -62,6 +62,16 @@ Terminal-based user interface: - **Memory Management**: Custom allocators including HLSF (Hybrid Layer Segregated Fit) - **Thread Pool Management**: Efficient task scheduling and execution +#### HLSF Benchmark Snapshot (BenchmarkDotNet) +Environment: Windows 11 (10.0.26100.7623), AMD Ryzen 7 6800H, .NET 8.0.23, x64 RyuJIT AVX2, GC = Concurrent Workstation. + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio | +|------- |-----:|-----:|------:|------:|-------:|----------:|-----------:| +| Hlsf | 838.4 µs | 16.67 µs | 40.58 µs | 1.00 | 0.12 | 64 B | 1.00 | +| Libc | 849.8 µs | 34.93 µs | 100.23 µs | 1.01 | 0.17 | 64 B | 1.00 | + +This shows HLSF matching libc allocation throughput under the same workload. See the HLSF document for details and caveats. + ### Graphics and Rendering - **OpenGL Integration**: Complete OpenGL API coverage - **Cross-Platform Support**: Windows and Linux compatibility diff --git a/StgSharp.Benchmark/HlsfAllocatorBench.cs b/StgSharp.Benchmark/HlsfAllocatorBench.cs new file mode 100644 index 00000000..c0221e5a --- /dev/null +++ b/StgSharp.Benchmark/HlsfAllocatorBench.cs @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="HlsfAllocatorBench" +// 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 BenchmarkDotNet.Attributes; +using StgSharp.HighPerformance.Memory; +using System.Runtime.InteropServices; + +namespace StgSharp.Benchmark +{ + [MemoryDiagnoser] + [InvocationCount(16)] + public class HlsfAllocatorBench + { + + private const int AllocationCount = 2048; + + private static readonly int[] AllocSizes = [ + 64, + 96, + 150, + 200, + 400, + 1024, + 4096 + ]; + private HybridLayerSegregatedFitAllocator _hlsfAllocator = null!; + private readonly List _hlsfLocalList = new(); + private readonly List _libcLocalList = new(); + private Random _random = new(42); + + [GlobalCleanup] + public void Cleanup() + { + _hlsfAllocator.Dispose(); + _hlsfLocalList.Clear(); + _hlsfLocalList.TrimExcess(); + _libcLocalList.Clear(); + _libcLocalList.TrimExcess(); + } + + [Benchmark] + public void Hlsf() + { + for (int i = 0; i < AllocationCount; i++) + { + lock (_hlsfAllocator) + { + HybridLayerSegregatedFitAllocationHandle allocate = _hlsfAllocator.Alloc(GetNextRandomSize()); + _hlsfLocalList.Add(allocate); + } + } + + for (int i = 0; i < _hlsfLocalList.Count; i++) + { + lock (_hlsfAllocator) { + _hlsfAllocator.Free(_hlsfLocalList[i]); + } + } + } + + public void InvocationSetup() + { + _hlsfLocalList.Clear(); + _libcLocalList.Clear(); + } + + [IterationSetup] + public void IterationSetup() + { + _hlsfLocalList.Clear(); + _libcLocalList.Clear(); + } + + [Benchmark(Baseline = true)] + public unsafe void Libc() + { + for (int i = 0; i < AllocationCount; i++) + { + void* allocate = NativeMemory.AlignedAlloc(GetNextRandomSize(), 64); + _libcLocalList.Add((nint)allocate); + } + + for (int i = 0; i < _libcLocalList.Count; i++) { + NativeMemory.AlignedFree((void*)_libcLocalList[i]); + } + } + + [GlobalSetup] + public void Setup() + { + _random = new Random(42); + _hlsfAllocator = new HybridLayerSegregatedFitAllocator(GetArenaBytes(), align:64); + } + + private static nuint GetArenaBytes() + { + const int defaultMb = 512; + const int minMb = 64; + int mb = defaultMb; + + string? env = Environment.GetEnvironmentVariable("HLSF_BENCH_ARENA_MB"); + if (!string.IsNullOrWhiteSpace(env) && + int.TryParse(env, out int parsed) && + parsed >= minMb) { + mb = parsed; + } + + return (nuint)mb * 1024u * 1024u; + } + + private uint GetNextRandomSize() => (uint)AllocSizes[_random.Next(AllocSizes.Length)]; + + } +} diff --git a/StgSharp.Benchmark/Program.cs b/StgSharp.Benchmark/Program.cs new file mode 100644 index 00000000..310d2d88 --- /dev/null +++ b/StgSharp.Benchmark/Program.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="Program" +// 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 BenchmarkDotNet.Running; +using StgSharp.Benchmark; + +namespace StgSharp.Benchmark +{ + internal static class Program + { + + private static void Main( + string[] args + ) + { + BenchmarkRunner.Run(); + } + + } +} diff --git a/StgSharp.Benchmark/StgSharp.Benchmark.csproj b/StgSharp.Benchmark/StgSharp.Benchmark.csproj new file mode 100644 index 00000000..f99ec316 --- /dev/null +++ b/StgSharp.Benchmark/StgSharp.Benchmark.csproj @@ -0,0 +1,32 @@ + + + Exe + net8.0 + enable + enable + true + x64 + x64 + + + + $(MSBuildProjectDirectory)\..\ + + + + + + + + + + + + + + + + + + + diff --git a/StgSharp.Common/HighPerformance/Memory/InroductionToHLSF.md b/StgSharp.Common/HighPerformance/Memory/InroductionToHLSF.md index c6ad2fb3..e8a09593 100644 --- a/StgSharp.Common/HighPerformance/Memory/InroductionToHLSF.md +++ b/StgSharp.Common/HighPerformance/Memory/InroductionToHLSF.md @@ -75,6 +75,19 @@ Notes: - Peak no-touch at 2MB (~17.99M alloc/s). Peak touch near 64B (~17.52M alloc/s). - Non-aligned sizes (e.g., 768B, 3KB) typically lower due to slicing/boundary handling. +### BenchmarkDotNet (HLSF vs Libc) + +Environment: .NET 8.0.23 (Release), x64 RyuJIT AVX2, Windows 11, AMD Ryzen 7 6800H. + +| Method | Mean | StdDev | Ratio | Allocated | +|------- |-----:|------:|------:|----------:| +| Hlsf | 838.4 µs | 40.6 µs | 1.00 | 64 B | +| Libc | 849.8 µs | 100.2 µs | 1.01 | 64 B | + +Interpretation: HLSF matches libc allocation throughput under the same workload, with slightly tighter dispersion in this run. + +> Note: BenchmarkDotNet also warned that the minimum iteration time was under 1ms; for more stable statistics, increase per-iteration work (e.g., higher `AllocationCount`). + ## Detailed Design (summary) - - Size classes: levelSizeArray = [64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216] (64B x 4^level) @@ -97,7 +110,7 @@ Notes: 1) Compute `level` and `segment` from requested `size` 2) Try pop bucket (`TryPopLevelForAllocation(level, segment, out entry)`) and escalate: - - Advance `segment` 0¡ú1¡ú2, then `level++`, until `MaxLevel` + - Advance `segment` 0��1��2, then `level++`, until `MaxLevel` - If still empty: try pop from overflow bucket (L=10, seg=1) or `TryAllocateNew32MBBlock` 3) On success: bound-check the slice offset; set `entry` to `Alloc` using `SetEntryLevel` 4) If remainder exists in the source region, call `SliceMemory(entry->NextNear, remainder)` to produce free chunks into buckets @@ -106,7 +119,7 @@ Notes: - Inputs: `next` entry position, `sizeToSlice` - Compute `offset` and `end` relative to base buffer; pick starting level by trailing-zero count of `offset`: - - `i = (BitOperations.TrailingZeroCount(offset) - 6) / 2`, clamped to `¡Ü MaxLevel` + - `i = (BitOperations.TrailingZeroCount(offset) - 6) / 2`, clamped to `�� MaxLevel` - Three phases to cover [offset, end): 1) Forward levels i..N-1: for each level size `size` and `upper=size*4`, align to `edge=((offset+upper-1)/upper)*upper`, pack `count = ((min(edge,end) - offset) / size)` blocks 2) If `i > MaxLevel`: pack in largest upper window on tail @@ -119,7 +132,7 @@ Notes: - Merge left while neighbor `p` is `Empty`, same `Level`, and within boundary (`AssertBoundary`) - Remove `p` from bucket (`RemoveFromBucket`), expand current, fix `Position`, unlink `p` - Merge right with neighbor `n` by the same rule - - Optionally promote `Level` if `Size ¡Ý levelSizeArray[originLevel] * 4` + - Optionally promote `Level` if `Size �� levelSizeArray[originLevel] * 4` - Ensure `BucketNode.EntryRef` points to the merged entry; caller re-enqueues into the proper bucket ### Buckets @@ -140,13 +153,13 @@ Notes: ### Complexity and Invariants -- Allocate/free are O(1) on average with bounded levels (¡Ü10) and constant-time bucket ops +- Allocate/free are O(1) on average with bounded levels (��10) and constant-time bucket ops - Invariants guarded by exceptions (e.g., `HLSFPositionChainBreakException`) and pointer consistency checks ## Profiling (hotspots, current workloads) -- SliceMemory ~22% (self ~17%) ¡ª slicing and bucket enqueue dominate allocation path -- MergeAndRemoveFromBuckets ~13% (self ~12%) ¡ª merge + removal dominate free path -- Collect ~7.6% (self ~7.5%) ¡ª low overall cost +- SliceMemory ~22% (self ~17%) �� slicing and bucket enqueue dominate allocation path +- MergeAndRemoveFromBuckets ~13% (self ~12%) �� merge + removal dominate free path +- Collect ~7.6% (self ~7.5%) �� low overall cost Focus: reduce per-slice math and bucket ops; add fast-paths for aligned regions; batch/lazy removals after merges. \ No newline at end of file diff --git a/StgSharp.Common/StgSharp.Common.csproj b/StgSharp.Common/StgSharp.Common.csproj index e3c96fde..1b42ebd5 100644 --- a/StgSharp.Common/StgSharp.Common.csproj +++ b/StgSharp.Common/StgSharp.Common.csproj @@ -128,6 +128,10 @@ https://github.com/Nitload-NSI/StgSharp.git + + $(MSBuildProjectDirectory)\..\ + + False @@ -136,11 +140,8 @@ False - - False - - - + + diff --git a/StgSharp.RegularAnalysis/RegularExpression.StateMachine.cs b/StgSharp.RegularAnalysis/RegularExpression.StateMachine.cs index 3f8e0d2f..f63c5228 100644 --- a/StgSharp.RegularAnalysis/RegularExpression.StateMachine.cs +++ b/StgSharp.RegularAnalysis/RegularExpression.StateMachine.cs @@ -94,6 +94,15 @@ namespace StgSharp.RegularAnalysis return node; } + public (RegularExpressionLeftBoarderNode left) AddBoarder(string groupName, int groupIndex) + { + RegularExpressionLeftBoarderNode leftNode = new RegularExpressionLeftBoarderNode(new GroupIndex(groupName, groupIndex)); + int leftIndex = _nodes.Count; + _nodes.Add(leftNode); + leftNode.FailNodeIndex = leftIndex; // Point to self by default + return (left: leftNode); + } + public RegularExpressionCounterNode AddCounter( int minCount, bool isEager diff --git a/StgSharpDevelop.sln b/StgSharpDevelop.sln index d7a8640e..a048db8f 100644 --- a/StgSharpDevelop.sln +++ b/StgSharpDevelop.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharpDebug", "StgSharpDe {3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF} = {3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF} EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Benchmark", "StgSharp.Benchmark\StgSharp.Benchmark.csproj", "{0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StgSharp.Native", "StgSharp.Native\StgSharp.Native.vcxproj", "{20CBFF26-6746-402E-992C-7B643E9423EB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0961B2BE-415C-45EA-8607-5B83623B2EF7}" @@ -65,6 +67,19 @@ Global {610B992B-BB34-49C3-9993-676C595FC12E}.Release|ARM64.ActiveCfg = Release|ARM64 {610B992B-BB34-49C3-9993-676C595FC12E}.Release|ARM64.Build.0 = Release|ARM64 {610B992B-BB34-49C3-9993-676C595FC12E}.Release|x64.ActiveCfg = Release|x64 + {610B992B-BB34-49C3-9993-676C595FC12E}.Release|x64.Build.0 = Release|x64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Debug|ARM64.Build.0 = Debug|ARM64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Debug|x64.ActiveCfg = Debug|x64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Debug|x64.Build.0 = Debug|x64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.PerformanceDebug|ARM64.ActiveCfg = PerformanceDebug|ARM64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.PerformanceDebug|ARM64.Build.0 = PerformanceDebug|ARM64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.PerformanceDebug|x64.ActiveCfg = Release|x64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.PerformanceDebug|x64.Build.0 = Release|x64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Release|ARM64.ActiveCfg = Release|ARM64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Release|ARM64.Build.0 = Release|ARM64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Release|x64.ActiveCfg = Release|x64 + {0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}.Release|x64.Build.0 = Release|x64 {20CBFF26-6746-402E-992C-7B643E9423EB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {20CBFF26-6746-402E-992C-7B643E9423EB}.Debug|ARM64.Build.0 = Debug|ARM64 {20CBFF26-6746-402E-992C-7B643E9423EB}.Debug|x64.ActiveCfg = Debug|x64