add a benchmark proj

This commit is contained in:
Youmu Liu
2026-01-28 20:30:22 +08:00
parent 3881078770
commit 29bee99355
9 changed files with 274 additions and 73 deletions
-61
View File
@@ -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)
+10
View File
@@ -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
+138
View File
@@ -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<HybridLayerSegregatedFitAllocationHandle> _hlsfLocalList = new();
private readonly List<nint> _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)];
}
}
+44
View File
@@ -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<HlsfAllocatorBench>();
}
}
}
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)'=='' Or '$(SolutionDir)'=='*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StgSharp.Common\StgSharp.Common.csproj" Properties="DisablePostBuildCopy=true" />
</ItemGroup>
<ItemGroup>
<NativeArtifacts Include="$(SolutionDir)build\**\StgSharp.Native*.dll" />
<NativeArtifacts Include="$(SolutionDir)build\**\StgSharp.Native*.pdb" />
</ItemGroup>
<Target Name="CopyNativeArtifacts" AfterTargets="Build" Condition="'$(DisableNativeCopy)'!='true'">
<Copy SourceFiles="@(NativeArtifacts)" DestinationFolder="$(TargetDir)" SkipUnchangedFiles="true" Condition="@(NativeArtifacts) != ''" />
</Target>
</Project>
@@ -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.
+6 -5
View File
@@ -128,6 +128,10 @@
<RepositoryUrl>https://github.com/Nitload-NSI/StgSharp.git</RepositoryUrl>
</PropertyGroup>
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)'=='' Or '$(SolutionDir)'=='*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='NoOps|AnyCPU'">
<Deterministic>False</Deterministic>
</PropertyGroup>
@@ -136,11 +140,8 @@
<Deterministic>False</Deterministic>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='NoOps|ARM64'">
<Deterministic>False</Deterministic>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(DisablePostBuildCopy)'!='true' And '$(SolutionDir)'!='' And '$(SolutionDir)'!='*Undefined*' And !$([System.String]::Copy('$(OutDir)').Contains('StgSharp.Benchmark'))">
<MakeDir Directories="$(SolutionDir)build" />
<Exec Command="xcopy /E /I /Y &quot;$(OutDir)&quot; &quot;$(SolutionDir)build&quot;" />
</Target>
@@ -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
+15
View File
@@ -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