Compare commits

..
54 Commits
Author SHA1 Message Date
Youmu Liu d1efb15e3f L4 theoretical work 2026-10-16 18:19:34 +08:00
Youmu Liu 91cf6a43cd Add L4 predictor queue skeleton 2026-10-15 18:15:38 +08:00
Youmu Liu 9867e1f262 Improve Swiss rehash and NUMA topology handling 2026-10-15 18:15:16 +08:00
Youmu Liu c64f7ae9f6 L4 theoretical work 2026-10-14 21:28:53 +08:00
Youmu Liu 6bc7224d4c L4 map use no sync fast route when enouge predict ahead 2026-10-13 18:14:16 +08:00
youmuliu b7f1d3d2ba after merge from main 2026-10-12 04:15:38 +08:00
youmuliu 76a9dfa5af after merge 2026-10-12 04:14:21 +08:00
youmuliu 2bef14493f before merge 2026-10-12 04:13:46 +08:00
Youmu Liu 0cef5aa517 Implement L4 Swiss map and benchmarks 2026-10-10 17:55:42 +08:00
Youmu Liu 172f64c6f9 new logo, and corrected readme 2026-10-10 09:54:22 +08:00
Youmu Liu 3a2b123478 swiss x86 is briefly ok, ready for test 2026-10-09 18:17:03 +08:00
Youmu Liu 2e7c3040f4 rewrite of swiss, swiss will not be public but l4 specific 2026-10-08 18:15:05 +08:00
Nitload ddd83b1b05 Merge pull request #10 from Nitload-NSI/copilot/add-documentation-magicspinner-chapters
[WIP] Add documentation for MagicSpinner's chapter design
2026-10-08 09:00:33 +08:00
Youmu Liu 1845a00390 few edit 2026-09-28 18:11:49 +08:00
youmuliu e24efd9f9c merge C base name prefix to nfn 2026-09-27 00:59:42 +08:00
youmuliu 9fc23c29fb no much coding work, mainly on theory work 2026-09-26 19:34:42 +08:00
Youmu Liu 194e869724 few adit 2026-09-23 18:19:33 +08:00
Youmu Liu 7ddfc36ac5 few adit 2026-09-21 20:39:43 +08:00
Youmu Liu 2c17f6c108 start nfa afterall 2026-09-20 18:29:44 +08:00
youmuliu 3d1d475e25 discard plan of text regex, discussing plan of LLM framework based on ngra and fuzzy automation theory 2026-09-20 01:12:26 +08:00
Youmu Liu 044b153463 add doc 2026-09-18 18:28:09 +08:00
Youmu Liu 6e6bd7794b add doc 2026-09-17 18:25:10 +08:00
Youmu Liu e04adf594a stop all work on text regex, ngra text regex is now frozen 2026-09-15 18:06:52 +08:00
Youmu Liu 57587bec16 use new exit lable 2026-09-14 18:21:23 +08:00
Youmu Liu 9e9078ced1 meet difficult problem on COUNT node 2026-09-11 17:30:31 +08:00
Youmu Liu 5a3286dc21 meet difficult problem on COUNT node 2026-09-11 17:29:42 +08:00
Youmu Liu f372f83171 process cases of ALT as special rules 2026-09-10 18:17:09 +08:00
youmuliu 7a820261cf Remove tracked OpenGL registry documents 2026-09-10 00:10:16 +08:00
youmuliu fa1186cd25 Readme for new namespace; New file for special expression format in text regex.
Signed-off-by: Nitload <Nitload_Creater@outlook.com>
2026-09-09 23:39:51 +08:00
Youmu Liu 8755185e78 specific file for special regex rule 2026-09-09 18:11:40 +08:00
Youmu Liu 8641fd4e08 Small changeds on text regex source gen 2026-09-08 18:10:36 +08:00
Youmu Liu 90c7538639 Graphic mat 44 is now generic; TextRegex source gen will use goto instead of do{}while() 2026-09-07 17:51:15 +08:00
youmuliu e49b14f037 adapt readme file
Signed-off-by: Nitload <Nitload_Creater@outlook.com>
2026-09-06 16:16:22 +08:00
youmuliu 052afa1dd4 after merghe from remote 2026-09-05 10:57:49 +08:00
Youmu Liu ee67dfbde1 rename ungenericed graphic vec usage to genericed 2026-09-04 18:51:27 +08:00
youmuliu 1d3d3c10dd after merge from remote 2026-09-04 00:39:27 +08:00
Youmu Liu 41ca31fd9a switch C code prefix to NIF 2026-09-03 20:04:20 +08:00
youmuliu 902be141d0 after merge from remote 2026-09-03 00:14:51 +08:00
youmuliu b5b7c75b5e before merge from remote 2026-09-03 00:13:04 +08:00
Youmu Liu 7c7101ee48 switch root namespace name to NITLOAD 2026-09-02 18:15:03 +08:00
Youmu Liu 4218baad48 generator memthod: COUNT() 2026-09-01 18:12:11 +08:00
Youmu Liu e52d0ac6f3 generate glenum, link glenum, add source gen for text regex 2026-08-31 18:12:44 +08:00
youmuliu 667b8d8e43 remove unused external doc files
Signed-off-by: Nitload <Nitload_Creater@outlook.com>
2026-08-30 04:30:52 +08:00
Nitload 5d680542a8 New opengl api binding and new graphic purpose vector design
Signed-off-by: Nitload <YoumuLiu_NSI@whv0t.onmicrosoft.com>
2026-08-29 23:10:44 +08:00
Youmu Liu 0b2593a5f6 genericed graphic vector design 2026-08-28 18:09:56 +08:00
Youmu Liu 3cba929599 improve gl api doc 2026-08-27 18:24:55 +08:00
Youmu Liu 648cac9d9b opengl source gen tool works 2026-08-27 15:05:43 +08:00
Youmu Liu 5db9f39a46 make new opengl source generator 2026-08-26 20:19:25 +08:00
Youmu Liu 75bf7fc139 make new opengl source generator 2026-08-26 18:12:08 +08:00
Youmu Liu 65041ff8bc make some changes to opengl api gen, discussing on rewrite of all opengl binding, making it neat and structral 2026-08-25 17:33:45 +08:00
Youmu Liu 708a915549 add more generate method of ALT node, add improve advise for gl source gen 2026-08-24 18:12:36 +08:00
Youmu Liu f0b6a4038e remove useless node from source gen pipeline. add source gen logic 2026-08-21 18:31:31 +08:00
Youmu Liu 968a6afe1a world timer supports numa and thread affinity binding 2026-08-20 16:53:34 +08:00
copilot-swe-agent[bot] aa7394503e Initial plan 2026-04-02 02:12:07 +00:00
611 changed files with 33264 additions and 93103 deletions
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Root Type="DevExpress.CodeRush.Formatting.UI.Options.BracesOptionsContainer">
<Options Language="C#">
<Options>
<Item Id="{3920243c-1bac-46ce-960d-e4186d99c187}" Type="DevExpress.CodeRush.Formatting.UI.Options.Conversion.PropertyBracesOption">
<WrapOpenBrace>
<Option>Wrap</Option>
</WrapOpenBrace>
<WrapCloseBrace>
<Option>Wrap</Option>
</WrapCloseBrace>
</Item>
<Item Id="{692dd27b-3066-4dd3-beac-516a569f5203}" Type="DevExpress.CodeRush.Formatting.UI.Options.Conversion.SimplePropertyBracesOption">
<WrapOpenBrace>
<Option>Wrap</Option>
</WrapOpenBrace>
<WrapCloseBrace>
<Option>Wrap</Option>
</WrapCloseBrace>
</Item>
</Options>
</Options>
</Root>
+3 -10
View File
@@ -1,12 +1,5 @@
[*]
end_of_line = lf
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[*.{bat,cmd,ps1}]
end_of_line = crlf
root = true
[*.cs]
# CS1591: 缺少对公共可见类型或成员的 XML 注释
dotnet_diagnostic.CS1591.severity = suggestion
dotnet_diagnostic.IDE0032.severity = none
dotnet_diagnostic.IDE0055.severity = none
+7 -2
View File
@@ -28,5 +28,10 @@ build.ninja
.cache/
BenchmarkDotNet.Artifacts/
/StgSharp.Native/lib/
!/StgSharp.Native/lib/**/CMakeLists.txt
# Khronos OpenGL Registry input is supplied locally; keep only its instructions.
/tools/*GenerateGL/xml/*
!/tools/Nitload.GenerateGL/xml/README.md
/src/Nitload.Native/lib/*
!/src/Nitload.Native/lib/**/
!/src/Nitload.Native/lib/**/CMakeLists.txt
+1 -1
View File
@@ -51,7 +51,7 @@
<SolutionRoot>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'Directory.Build.props'))\</SolutionRoot>
<!-- native 库名称 -->
<NativeLibName>StgSharp.Native</NativeLibName>
<NativeLibName>Nitload.Native</NativeLibName>
</PropertyGroup>
<!-- ════════════════════════════════════════════════════════ -->
+32 -9
View File
@@ -1,19 +1,37 @@
<Project>
<!-- Native builds are opt-in for executable hosts, not repeated by every library project. -->
<Target Name="BuildNativeLibrary"
BeforeTargets="PrepareForBuild"
Condition="'$(BuildNativeBeforeManaged)'=='true' And '$(DesignTimeBuild)'!='true'">
<PropertyGroup>
<NativeCMakePreset Condition="'$(NativeCMakePreset)'=='' And '$(Configuration)'=='Debug'">clang-debug</NativeCMakePreset>
<NativeCMakePreset Condition="'$(NativeCMakePreset)'==''">clang-release</NativeCMakePreset>
<NativeCMakeBuildDir Condition="'$(NativeCMakeBuildDir)'==''">$(SolutionRoot)cmake_build/$(NativeCMakePreset)</NativeCMakeBuildDir>
<_NativeCMakeOutputDir>$([System.String]::Copy('$(NativeBuildDir)').Replace('\', '/'))</_NativeCMakeOutputDir>
</PropertyGroup>
<Exec Command="cmake --preset &quot;$(NativeCMakePreset)&quot; -S &quot;$(SolutionRoot)src/Nitload.Native&quot; -B &quot;$(NativeCMakeBuildDir)&quot; &quot;-DNFN_BUILD_OUTPUT_DIR:PATH=$(_NativeCMakeOutputDir)&quot; &quot;-DNFN_EXPECTED_RID:STRING=$(BuildRid)&quot;" />
<Exec Command="cmake --build &quot;$(NativeCMakeBuildDir)&quot; --target &quot;$(NativeLibName)&quot;" />
<Error Condition="!Exists('$(NativeBuildDir)$(NativeFileName)')"
Text="CMake did not produce the expected native library: $(NativeBuildDir)$(NativeFileName). Check the target OS, architecture and output name." />
</Target>
<!-- ════════════════════════════════════════════════════════ -->
<!-- 编译后:把 C# 输出拷贝到 bin/<rid>/ 目录 -->
<!-- 替代原来的 xcopy PostBuild -->
<!-- ════════════════════════════════════════════════════════ -->
<Target Name="CopyOutputToBuildDir"
AfterTargets="Build"
Condition="'$(DisablePostBuildCopy)'!='true'">
Condition="'$(DisablePostBuildCopy)'!='true' And '$(MSBuildProjectExtension)'=='.csproj' And '$(DesignTimeBuild)'!='true'">
<MakeDir Directories="$(ManagedBuildDir)" />
<!-- 拷贝所有编译输出(DLL + PDB + XML + deps.json) -->
<ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*.*"
Exclude="$(OutputPath)**\*.user" />
<!-- Native artifacts flow from CMake to hosts only. Never copy an old deployed DLL
back over the authoritative CMake output, including pre-rename artifacts. -->
<_OutputFiles Include="$(TargetDir)**\*.*"
Exclude="$(TargetDir)**\*.user;$(TargetDir)**\$(NativeLibName).*;$(TargetDir)**\lib$(NativeLibName).*;$(TargetDir)**\StgSharp.Native.*;$(TargetDir)**\libStgSharp.Native.*" />
</ItemGroup>
<Copy SourceFiles="@(_OutputFiles)"
@@ -21,7 +39,7 @@
SkipUnchangedFiles="true" />
<Message Importance="high"
Text="[StgSharp] $(MSBuildProjectName) → $(ManagedBuildDir)" />
Text="[Nitload] $(MSBuildProjectName) → $(ManagedBuildDir)" />
</Target>
<!-- ════════════════════════════════════════════════════════ -->
@@ -29,7 +47,12 @@
<!-- 这样 F5 调试时 P/Invoke 能找到 native -->
<!-- ════════════════════════════════════════════════════════ -->
<Target Name="CopyNativeToProjectOutput"
AfterTargets="Build">
AfterTargets="Build"
BeforeTargets="CopyOutputToBuildDir"
Condition="'$(DisableNativeCopy)'!='true' And '$(MSBuildProjectExtension)'=='.csproj' And '$(DesignTimeBuild)'!='true'">
<Error Text="Native library not found at $(NativeBuildDir)$(NativeFileName). Build Nitload.Native with CMake, or enable BuildNativeBeforeManaged."
Condition="'$(RequireNativeLibrary)'=='true' And !Exists('$(NativeBuildDir)$(NativeFileName)')" />
<!-- native 库文件 -->
<ItemGroup>
@@ -44,21 +67,21 @@
</ItemGroup>
<Copy SourceFiles="@(_NativeLibFile);@(_NativeSymFile)"
DestinationFolder="$(OutputPath)"
DestinationFolder="$(TargetDir)"
SkipUnchangedFiles="true"
Condition="'@(_NativeLibFile)'!='' Or '@(_NativeSymFile)'!=''" />
<Message Importance="high"
Text="[StgSharp] Native $(NativeFileName) ($(BuildRid)) → $(OutputPath)"
Text="[Nitload] Native $(NativeFileName) ($(BuildRid)) → $(TargetDir)"
Condition="'@(_NativeLibFile)'!=''" />
<Warning Text="[StgSharp] Native library not found at $(NativeBuildDir)$(NativeFileName). Build StgSharp.Native first!"
<Warning Text="[Nitload] Native library not found at $(NativeBuildDir)$(NativeFileName). Build Nitload.Native first!"
Condition="!Exists('$(NativeBuildDir)$(NativeFileName)')" />
</Target>
<!-- ════════════════════════════════════════════════════════ -->
<!-- NuGet 打包:把所有已存在的 RID native 放进 runtimes/ -->
<!-- 只在 StgSharp.Common 里生效(通过 IsNativeHost 属性) -->
<!-- 只在 Nitload.Common 里生效(通过 IsNativeHost 属性) -->
<!-- ════════════════════════════════════════════════════════ -->
<Target Name="CollectNativeForPack"
BeforeTargets="GenerateNuspec"
-37
View File
@@ -1,37 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36705.20
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateGL", "test\StgSharp.GenerateGL\GenerateGL.csproj", "{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StgSharp.Common", "src\StgSharp.Common\StgSharp.Common.csproj", "{5D646C0E-CCF8-B5B4-7DE8-1C37542E7B4D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Debug|x64.ActiveCfg = Debug|x64
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Debug|x64.Build.0 = Debug|x64
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Release|Any CPU.Build.0 = Release|Any CPU
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Release|x64.ActiveCfg = Release|x64
{9FD27EE6-E94D-4E64-A13B-5A349DEA215A}.Release|x64.Build.0 = Release|x64
{5D646C0E-CCF8-B5B4-7DE8-1C37542E7B4D}.Debug|Any CPU.ActiveCfg = Debug|x64
{5D646C0E-CCF8-B5B4-7DE8-1C37542E7B4D}.Debug|x64.ActiveCfg = Debug|x64
{5D646C0E-CCF8-B5B4-7DE8-1C37542E7B4D}.Release|Any CPU.ActiveCfg = Release|x64
{5D646C0E-CCF8-B5B4-7DE8-1C37542E7B4D}.Release|x64.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AC71C01F-9867-4E43-85AB-4A8A7896B1BC}
EndGlobalSection
EndGlobal
+4 -4
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Nitload StgSharp Team
Copyright (c) 2024 Nitload
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -20,9 +20,9 @@ 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.
StgSharp program uses the following open source projects,
I'd like to refer to them here and show my sincere appreciation,
while the names are sorted in no particular order
The nFramework project uses the following open-source projects.
Their respective license notices are reproduced below,
and the projects are listed in no particular order.
1. GLFW
+7 -7
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<package >
<metadata>
<id>StgSharp</id>
<id>Nitload</id>
<version>1.0.0</version>
<title>StgSharp</title>
<title>Nitload</title>
<authors>Nitload</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
@@ -11,12 +11,12 @@
<projectUrl>https://github.com/Nitload-NSI/StgSharp</projectUrl>
<description>No des right now</description>
<releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
<copyright>Nitload reserve</copyright>
<copyright>Copyright (c) 2024 Nitload</copyright>
<tags>OpenGL C#</tags>
</metadata>
<files>
<file src="StgSharp\bin\x64\Release\netstandard2.1\StgSharp.dll" target="lib\netstandard2.1\StgSharp.dll" />
<file src="StgSharp\bin\x64\Release\netstandard2.1\StgSharp.xml" target="lib\netstandard2.1\StgSharp.xml" />
<file src="StgSharp\bin\x64\Release\netstandard2.1\StgSharpC.dll" target="lib\netstandard2.1\StgSharpC.dll" />
<file src="src\Nitload.Common\bin\x64\Release\net8.0\Nitload.Common.dll" target="lib\net8.0\Nitload.Common.dll" />
<file src="src\Nitload.Common\bin\x64\Release\net8.0\Nitload.Common.xml" target="lib\net8.0\Nitload.Common.xml" />
<file src="bin\win-x64\Nitload.Native.dll" target="runtimes\win-x64\native\Nitload.Native.dll" />
</files>
</package>
</package>
+8 -8
View File
@@ -3,27 +3,27 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.3.11520.95 d18.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Common", "src\StgSharp.Common\StgSharp.Common.csproj", "{3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Common", "src\Nitload.Common\Nitload.Common.csproj", "{3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF}"
ProjectSection(ProjectDependencies) = postProject
{20CBFF26-6746-402E-992C-7B643E9423EB} = {20CBFF26-6746-402E-992C-7B643E9423EB}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StgSharp.Native", "src\StgSharp.Native\StgSharp.Native.vcxproj", "{20CBFF26-6746-402E-992C-7B643E9423EB}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Nitload.Native", "src\Nitload.Native\Nitload.Native.vcxproj", "{20CBFF26-6746-402E-992C-7B643E9423EB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0961B2BE-415C-45EA-8607-5B83623B2EF7}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.TerminalDialogue", "src\StgSharp.TerminalDialogue\StgSharp.TerminalDialogue.csproj", "{10B5819F-2A62-41B6-980A-EB9E76A0E95C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.TerminalDialogue", "src\Nitload.TerminalDialogue\Nitload.TerminalDialogue.csproj", "{10B5819F-2A62-41B6-980A-EB9E76A0E95C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Model", "src\StgSharp.Model\StgSharp.Model.csproj", "{D9538DE0-EE4C-4183-86D6-7DFC08A7BDFB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Model", "src\Nitload.Model\Nitload.Model.csproj", "{D9538DE0-EE4C-4183-86D6-7DFC08A7BDFB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Mathematics", "src\StgSharp.Mathematics\StgSharp.Mathematics.csproj", "{9946599F-4D34-440F-840D-F9443D5AEFAA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Mathematics", "src\Nitload.Mathematics\Nitload.Mathematics.csproj", "{9946599F-4D34-440F-840D-F9443D5AEFAA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.RegularAnalysis", "src\StgSharp.RegularAnalysis\StgSharp.RegularAnalysis.csproj", "{46339874-C6B8-4361-997E-2E30387E51F9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.RegularAnalysis", "src\Nitload.RegularAnalysis\Nitload.RegularAnalysis.csproj", "{46339874-C6B8-4361-997E-2E30387E51F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Graphics", "src\StgSharp.Graphics\StgSharp.Graphics.csproj", "{80CD3DDE-0642-451D-82DF-0DE422FC6E34}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Graphics", "src\Nitload.Graphics\Nitload.Graphics.csproj", "{80CD3DDE-0642-451D-82DF-0DE422FC6E34}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -98,4 +98,4 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E13615C2-4C8D-4072-95A1-124440983818}
EndGlobalSection
EndGlobal
EndGlobal
+10 -10
View File
@@ -9,32 +9,32 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharpDebug", "test\StgSh
{20CBFF26-6746-402E-992C-7B643E9423EB} = {20CBFF26-6746-402E-992C-7B643E9423EB}
{3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF} = {3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0961B2BE-415C-45EA-8607-5B83623B2EF7}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Common", "src\StgSharp.Common\StgSharp.Common.csproj", "{3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Common", "src\Nitload.Common\Nitload.Common.csproj", "{3EAF2EE6-FA9E-4F57-951F-7E6F8125DFBF}"
ProjectSection(ProjectDependencies) = postProject
{20CBFF26-6746-402E-992C-7B643E9423EB} = {20CBFF26-6746-402E-992C-7B643E9423EB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Benchmark", "src\Nitload.Benchmark\Nitload.Benchmark.csproj", "{0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Benchmark", "src\StgSharp.Benchmark\StgSharp.Benchmark.csproj", "{0D4D2A8F-2B5B-4F7D-8D63-4A9AF4B4C782}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Nitload.Native", "src\Nitload.Native\Nitload.Native.vcxproj", "{20CBFF26-6746-402E-992C-7B643E9423EB}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StgSharp.Native", "src\StgSharp.Native\StgSharp.Native.vcxproj", "{20CBFF26-6746-402E-992C-7B643E9423EB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.TerminalDialogue", "src\Nitload.TerminalDialogue\Nitload.TerminalDialogue.csproj", "{10B5819F-2A62-41B6-980A-EB9E76A0E95C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.TerminalDialogue", "src\StgSharp.TerminalDialogue\StgSharp.TerminalDialogue.csproj", "{10B5819F-2A62-41B6-980A-EB9E76A0E95C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Model", "src\Nitload.Model\Nitload.Model.csproj", "{D9538DE0-EE4C-4183-86D6-7DFC08A7BDFB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Model", "src\StgSharp.Model\StgSharp.Model.csproj", "{D9538DE0-EE4C-4183-86D6-7DFC08A7BDFB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Mathematics", "src\Nitload.Mathematics\Nitload.Mathematics.csproj", "{9946599F-4D34-440F-840D-F9443D5AEFAA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Mathematics", "src\StgSharp.Mathematics\StgSharp.Mathematics.csproj", "{9946599F-4D34-440F-840D-F9443D5AEFAA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.RegularAnalysis", "src\Nitload.RegularAnalysis\Nitload.RegularAnalysis.csproj", "{46339874-C6B8-4361-997E-2E30387E51F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.RegularAnalysis", "src\StgSharp.RegularAnalysis\StgSharp.RegularAnalysis.csproj", "{46339874-C6B8-4361-997E-2E30387E51F9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitload.Graphics", "src\Nitload.Graphics\Nitload.Graphics.csproj", "{80CD3DDE-0642-451D-82DF-0DE422FC6E34}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StgSharp.Graphics", "src\StgSharp.Graphics\StgSharp.Graphics.csproj", "{80CD3DDE-0642-451D-82DF-0DE422FC6E34}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StgSharp.UserInterface", "src\StgSharp.UserInterface\StgSharp.UserInterface.csproj", "{E5F7BEEE-D666-F614-7FF7-55C11C810254}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitload.UserInterface", "src\Nitload.UserInterface\Nitload.UserInterface.csproj", "{E5F7BEEE-D666-F614-7FF7-55C11C810254}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+39 -27
View File
@@ -1,16 +1,16 @@
# StgSharp
![nFramework Logo](logo/nFramework-horizontal-light.png)
![StgSharp Logo](STG%23LOGO.png)
# nFramework
StgSharp is an experimental .NET framework for graphics, numerical computing, language analysis, and other performance-sensitive applications. It began as the foundation of a next-generation STG (shoot 'em up) engine, but the current repository focuses on reusable low-level infrastructure rather than a complete, ready-to-use game engine.
nFramework (formerly StgSharp, now also nitload.Framework) is an experimental framework built on .NET for graphics, numerical computing, language analysis, and other performance-sensitive applications. It combines managed C# libraries with a C native runtime, targets .NET 8, and provides Windows and Linux native implementations.
## Overview
The framework combines managed C# APIs with a C native library. Its current work includes OpenGL bindings and rendering infrastructure, generic matrix and vector types, SIMD-accelerated native kernels, custom allocators and collections, regular-language analysis, and state-oriented UI infrastructure. StgSharp targets .NET 8; platform and architecture coverage varies by module, with Windows and Linux as the principal native targets.
nFramework combines managed C# APIs with a C native library. Its current work includes OpenGL bindings and rendering infrastructure, generic matrix and vector types, SIMD-accelerated native kernels, custom allocators and collections, regular-language analysis, and state-oriented UI infrastructure. nFramework targets .NET 8; platform and architecture coverage varies by module, with Windows and Linux as the principal native targets.
## Core Modules
### StgSharp.Common
### Nitload.Common
The foundational library containing shared functionality:
@@ -20,7 +20,7 @@ The foundational library containing shared functionality:
- **Pipeline and State**: Processing pipelines and reusable state-machine primitives
- **Environment**: Platform abstraction and system integration
### StgSharp.Mathematics (Numeric)
### Nitload.Mathematics (Numeric)
Numerics and linear algebra built on top of the core library:
@@ -28,17 +28,29 @@ Numerics and linear algebra built on top of the core library:
- **Numeric Utilities**: Core numeric helpers and algorithms
- **SIMD-Friendly Kernels**: Data layouts optimized for hardware acceleration
### StgSharp.RegularAnalysis
### Nitload.RegularAnalysis
Nitload General Regular Analysis (NGRA) infrastructure for recognizing and processing regular languages:
- **Regex/Automata Core**: Regular expression processing and automata utilities
- **NFA Direction**: Planned generic ε-NFA sequence execution, followed by weighted automata and NGRA-Tensor research
- **Parsing Primitives**: Reusable tokenizer and grammar building blocks
- **Analysis Pipeline**: Structured processing for language-like inputs
NGRA is a foundation for higher-level language tooling. It does not replace `StgSharp.Script`; the planned Script implementation depends on NGRA and has not started yet. The source currently under `StgSharp.Script` is retained from the earlier EXPRESS implementation and is not part of the active core solution.
NGRA is a foundation for higher-level language tooling. It does not replace `Nitload.Script`; the planned Script implementation depends on NGRA and has not started yet. The source currently under `Nitload.Script` is retained from the earlier EXPRESS implementation and is not part of the active core solution.
### StgSharp.Graphics
The former Text Regex parser, matcher, and source generator are [archived](src/Nitload.RegularAnalysis/Legacy/README.md)
and excluded from compilation. Generic AST and token infrastructure remain active.
Although Text Regex development is frozen, it produced valuable reusable results: the five-link
AST model, tree normalization and merging techniques, token/analysis infrastructure, and practical
experience with COUNT continuations, group boundaries, and rollback. These results remain available
to inform the new NFA architecture.
Future string-regex functionality will use .NET `GeneratedRegex`. NGRA will no longer pursue
custom `Span<char>` or SIMD optimizations for string matching; its research focus is generalized
sequence analysis and, later, weighted relation networks.
### Nitload.Graphics
Managed graphics infrastructure built on Common and Mathematics:
@@ -47,7 +59,7 @@ Managed graphics infrastructure built on Common and Mathematics:
- **Shader Support**: Shader compilation, program management, and shader-generation utilities
- **Window Integration**: GLFW-based window and input access
### StgSharp.Native
### Nitload.Native
Native library integration:
@@ -56,7 +68,7 @@ Native library integration:
- **Matrix Kernels**: Optimized matrix computation routines
- **Platform Runtime**: Native thread and platform support for Windows and Linux
### StgSharp.TerminalDialogue
### Nitload.TerminalDialogue
Terminal-based user interface application and components:
@@ -64,7 +76,7 @@ Terminal-based user interface application and components:
- **Dialogue System**: User interaction and input handling
- **Terminal Graphics**: Text-based UI rendering
> Note: `StgSharp.Script` is planned as a consumer of NGRA and has not started yet. Its existing EXPRESS sources belong to the earlier implementation. `StgSharp.Model` is currently an empty placeholder while its design is being reconsidered.
> Note: `Nitload.Script` is planned as a consumer of NGRA and has not started yet. Its existing EXPRESS sources belong to the earlier implementation. `Nitload.Model` is currently an empty placeholder while its design is being reconsidered.
## Key Features
@@ -113,7 +125,7 @@ The benchmark reports normalized time per allocation/free operation. Each iterat
### Data Processing
- **NGRA**: Regex analysis, intermediate representation, optimization, and source generation
- **EXPRESS (legacy/inactive)**: The repository retains an earlier experimental EXPRESS parser/compiler under `StgSharp.Script`
- **EXPRESS (legacy/inactive)**: The repository retains an earlier experimental EXPRESS parser/compiler under `Nitload.Script`
- **Script (planned)**: A future language layer intended to build on NGRA; development has not started
## Requirements
@@ -125,7 +137,7 @@ The benchmark reports normalized time per allocation/free operation. Each iterat
## Installation
StgSharp is currently in development and requires compilation from source. **NuGet packages are not yet available** - you must build the project from source code.
nFramework is currently in development and requires compilation from source. **NuGet packages are not yet available** - you must build the project from source code.
### Prerequisites
@@ -147,7 +159,7 @@ StgSharp is currently in development and requires compilation from source. **NuG
2. **Build the native library (recommended toolchain)**:
```bash
cmake --preset clang-release -S src/StgSharp.Native
cmake --preset clang-release -S src/Nitload.Native
cmake --build cmake_build/clang-release
```
@@ -156,10 +168,10 @@ StgSharp is currently in development and requires compilation from source. **NuG
3. **Build the managed projects**:
```bash
dotnet build StgSharp.sln
dotnet build Nitload.sln
```
Managed builds copy an existing native binary into their output directory. They do not invoke CMake automatically, so build `StgSharp.Native` first when native functionality is required.
Managed library builds copy an existing native binary into their output directory. Build `Nitload.Native` first when native functionality is required. The `StgSharpDebug` executable is an exception: its project enables an incremental CMake build before managed compilation and requires the native DLL for the normal runtime path.
### Build Configuration
@@ -168,7 +180,7 @@ StgSharp is currently in development and requires compilation from source. **NuG
### Notes
- CMake is the maintained build path for `StgSharp.Native`; the checked-in presets primarily target Clang
- CMake is the maintained build path for `Nitload.Native`; the checked-in presets primarily target Clang
- The Visual Studio C project may be opened with MSVC tooling, but MSVC IntelliSense behavior and MSVC-produced binaries are not maintained or guaranteed
- Windows and Linux platform implementations exist; actual feature and architecture coverage may differ
- x86-64 currently has the most complete optimized kernel coverage
@@ -187,8 +199,8 @@ StgSharp is currently in development and requires compilation from source. **NuG
### Matrix Operations
```csharp
using StgSharp.HighPerformance.Memory;
using StgSharp.Mathematics.Numeric;
using Nitload.Common.HighPerformance.Memory;
using Nitload.Mathematics.Numeric;
// Matrix storage is allocated explicitly from a native-memory arena.
using var allocator = new TwoLayerSegregatedFitAllocator(64 * 1024 * 1024);
@@ -207,7 +219,7 @@ General matrix arithmetic does not currently provide operator overloads. Because
### Memory Management
```csharp
using StgSharp.HighPerformance.Memory;
using Nitload.Common.HighPerformance.Memory;
// Create a 64 MiB TLSF arena
using var allocator = new TwoLayerSegregatedFitAllocator(64 * 1024 * 1024);
@@ -225,13 +237,13 @@ allocator.Free(handle);
### Technical Documentation
- [TLSF Allocator](src/StgSharp.Common/HighPerformance/Memory/InroductionToTLSF.md) - Arena layout, allocation behavior, and benchmark methodology
- [Native Library Build](src/StgSharp.Native/README.md) - Native build and source-layout notes
- [Native Library Naming](src/StgSharp.Native/naming.md) - Native library file naming conventions
- [TLSF Allocator](doc/InroductionToTLSF.md) - Arena layout, allocation behavior, and benchmark methodology
- [Native Library Build](src/Nitload.Native/README.md) - Native build and source-layout notes
- [Native Library Naming](src/Nitload.Native/naming.md) - Native library file naming conventions
## Contributing
StgSharp is developed by Nitload Space. Contributions are welcome, particularly in:
nFramework is developed by Nitload Space under the Nitload brand. Contributions are welcome, particularly in:
- Performance optimizations
- Cross-platform compatibility
@@ -240,7 +252,7 @@ StgSharp is developed by Nitload Space. Contributions are welcome, particularly
## License
StgSharp is licensed under the MIT License. See [LICENSE](LICENSE) for details.
nFramework is licensed under the MIT License. See [LICENSE](LICENSE) for details.
## Future Plans
-6
View File
@@ -1,6 +0,0 @@
[
{
"Name": "新建配置文件",
"Projects": []
}
]
+916
View File
@@ -0,0 +1,916 @@
# NGRA 转型计划
> 面向本地 Codex / 项目维护者的快速架构说明
> 项目:Nitload General Regular Analyzer(NGRA)
## 1. 转型结论
NGRA 不再以“高性能字符串正则表达式引擎”为主要目标。
原先围绕 `Span<char>`、专用字符串匹配、复杂回溯、Source Generator 等方向继续投入的收益已经明显下降。后续核心工作转向:
1. 以 **ε-NFA / NFA** 为基础执行模型;
2. 将输入从 `char` 推广为任意可比较、可判定类别的序列元素;
3. 面向 Token Stream、语法前端、半上下文模式分析等场景;
4. 支持自动机归并、分层自动机、状态/断言/前序引用;
5. 在此基础上继续研究 **Weighted Automaton / NGRA-Tensor**。
因此,Text Regex 从“主架构”降级为“兼容前端 / 参考实现 / 测试子系统”。
---
## 2. 当前代码处理原则
### 2.1 保留现有 Text Regex 半成品
当前 Text Regex Source Generator、COUNT/CONCAT/ALT lowering、生成式 matcher 等代码不删除。
原因:
- 五角星 AST 模型仍然是有效成果;
- AST 搜索、规范化、旋转、合并等代码仍可复用;
- 当前实现记录了 COUNT continuation、group boundary、rollback 等真实工程问题;
- 可作为未来自动机 IR 与旧实现行为的对照;
- 删除会丢失大量探索成果。
建议:
- 将旧生成器移动到 `Legacy` / `Experimental` / `Archive` 目录;
- 通过 `.csproj` 的 `Compile Remove` 或独立非构建项目,使其暂时不参与主编译;
- 不再优先修补完整 `.NET Regex` 兼容性。
### 2.2 五角星 AST 继续保留
现有节点关系:
```text
Parent
↑
Previous ← Node → Next
↙ ↘
Left Right
```
继续作为通用语法树 / 结构树基础设施。
需要坚持:
- AST 用于表达语法和结构;
- 自动机 / CFG / Relation Graph 用于表达执行与传播;
- 不再要求 AST 本身承担全部 backtracking / continuation 语义。
---
## 3. 新主线:ε-NFA
第一阶段优先实现一个简单、正确、无性能野心的 ε-NFA 模拟器。
### 3.1 基础状态模型
```text
Current Active States
↓
ε-closure
↓
consume(input)
↓
Next Active States
↓
ε-closure
```
核心状态:
```text
State
Transition
StartState
AcceptStates
ActiveStateSet
```
Transition 分为:
```text
ε transition
matched transition
```
输入类型不能写死为 `char`。
建议:
```csharp
EpsilonNfa<TInput>
```
或:
```csharp
EpsilonNfa<TScalar>
```
匹配条件可以抽象为:
```csharp
bool Match(in TInput input)
```
或者后续升级为更明确的 Predicate / Category Matcher。
### 3.2 第一阶段支持的 Pattern
第一版只要求:
```text
literal / predicate
concat
alternation
*
+
?
{m,n}
ordinary grouping
```
暂不追求:
```text
capture rollback
复杂 greedy / lazy 行为
lookaround
backreference
完整 .NET Regex compatibility
```
其中 `{m,n}` 可以优先在构造期展开为 NFA 结构,而不是运行时维护复杂 COUNT 控制流。
---
## 4. 为什么以 NFA 为主,而不是 DFA
DFA 仍然有价值,但不再是必须经过的阶段。
核心路线:
```text
Pattern
↓
ε-NFA
↓
NFA execution
↓
Weighted NFA
↓
NGRA-Tensor
```
DFA 只作为可选 specialization:
```text
Boolean
Static
No dynamic relation
No weighted state
```
满足这些条件的局部子图,可以由编译器确定化为 DFA fast path。
总体原则:
> NFA 是主语义模型,DFA 是可选优化后端。
原因:
普通 NFA 的活动状态:
```text
a_i ∈ {0,1}
```
加权机可自然推广为:
```text
a_i ∈ R
```
Tensor 版本可以继续推广为:
```text
a_i ∈ R^d
```
多状态同时活跃不再是需要“消除的非确定性”,而会成为模型本身的状态表示。
---
## 5. NGRA 的目标:广义序列分析
NGRA 不再局限于字符串。
典型输入:
```text
char
token
syntax node
semantic node
sensor frame
event
instruction-like object
```
Pattern 的作用也从:
```text
“这个字符串是不是符合 regex”
```
扩展为:
```text
“这个序列在当前状态、历史、类别和上下文条件下,
是否满足某个结构化模式”
```
---
## 6. 半上下文 Token Analyzer
NGRA 的近期目标之一,是成为一种介于普通 Regex 与完整 Parser 之间的 Token Stream Analyzer。
可用能力包括:
```text
Token 类别匹配
Token 属性匹配
前序状态引用
局部断言
Pattern 状态
符号绑定
分层子自动机
局部上下文
```
因此它适合作为:
```text
Lexer 后端
Token Regex
语法前端
编译器预分析器
DSL Parser Frontend
结构化流分析器
```
“半上下文”的含义不是严格的形式语言分类,而是:
> Pattern 不只依赖当前 token,也可以依赖有限的已知状态、绑定、前序结果和结构信息,但不要求一开始就构造完整通用 Parser。
---
## 7. 自动机归并
大量 Pattern 应优先在 NFA 层归并,而不是各自独立执行。
例如:
```text
ε → Pattern A
/
GlobalStart ─ε → Pattern B
\
ε → Pattern C
```
Accepting State 可以附带:
```text
PatternId
MatchKind
Payload
```
后续目标:
- 合并公共路径;
- 消除冗余 ε 边;
- 状态压缩;
- bitset active state;
- pattern priority;
- 多层自动机组合。
---
## 8. 分层自动机
后续重点不是继续增加字符串 Regex 特性,而是建立分层自动机。
示意:
```text
Scalar / Token Layer
↓
Local Pattern Layer
↓
Phrase / Region Layer
↓
Sentence / Structure Layer
↓
Semantic / Relation Layer
```
低层自动机可以生成高层节点,高层节点继续作为新序列输入。
即:
```text
Input Stream
↓
Automaton L0
↓
Derived Nodes
↓
Automaton L1
↓
Higher-level Nodes
```
这应成为 NGRA 进入编译器前端、自然语言结构分析和复杂流分析的主路径。
---
## 9. Assertion / Context 的重新定义
不再围绕字符串 Regex 的 lookahead / lookbehind 设计核心架构。
通用定义应更接近:
```text
Assert(Context) -> bool
```
或者:
```text
RelationGate(State, Context, History) -> bool
```
因此:
- 字符串前瞻/后顾只是特殊情况;
- 前序引用只是状态引用的一种;
- group/capture 不是核心抽象;
- Context / Binding / Region / History 才是可推广概念。
---
# NGRA-Tensor 构想
## 10. 基本方向
NGRA-Tensor 不是简单地“给 NFA 边加 float”。
它希望把:
```text
Boolean multi-state automaton
```
推广为:
```text
Weighted / Tensor Relation Network
```
基础演化:
```text
NFA
a_i ∈ {0,1}
↓ weighted
Weighted Automaton
a_i ∈ R
↓ tensor/state
NGRA-Tensor
a_i ∈ R^d
```
---
## 11. Relation 成为一等对象
不再采用固定 `Port4` 作为核心语义。
推荐模型:
```text
Source Node
↓
[ Relation ]
↓
Destination Node
```
Relation 包含:
```text
Source
Destination
Type
Parameters
Weight / Weight Function
Optional State
Eligibility / Gate
```
语义上:
```text
Relation:
决定一条联系是否生效,
以及它如何把 Source 信息贡献给 Destination。
```
Node 负责保存状态。
Blend 负责汇合多条 Relation 的贡献。
---
## 12. Pull,而不是主动广播
GPU / 执行层优先采用 destination pull:
```text
Destination Node
↓
请求自己依赖的 Relations
↓
读取 Source states
↓
Relation Eval
↓
Blend
```
而不是:
```text
Source Node
↓
主动向大量 Destination 广播
```
优点:
- 减少 scatter;
- 减少原子写冲突;
- 减少 readiness 管理;
- 更适合 GPU destination-centric execution。
---
## 13. Relation 的三段语义
推荐拆成:
```text
Eligibility
↓
Compatibility
↓
Transfer
```
即:
1. **Eligibility**:这条 Relation 当前能不能参与;
2. **Compatibility**:参与后有多重要;
3. **Transfer**:真正传递什么信息。
形式上:
```text
gate_ij = G(state, context, relation)
score_ij = F(query, key, relation)
value_ij = T(source_state, relation)
```
最终:
```text
output_i = Blend(gate_ij * weight_ij * value_ij)
```
---
## 14. 与 Attention / QKV 的关系
QKV 可以作为 Relation 内部算子的第一种 baseline,但不应决定 NGRA-Tensor 的外层拓扑。
标准 Attention:
```text
Q = “我需要什么”
K = “我适不适合被引用”
V = “真正传递的内容”
```
数学上:
```text
score_ij = q_i · k_j
weight_ij = softmax(score_ij)
output_i = Σ weight_ij * v_j
```
NGRA-Tensor 的差异:
- Relation 是显式、长期存在、可带类型的;
- 候选关系通常是稀疏的;
- 不一定对全部 node 做 dense attention;
- Relation 可以有自己的参数、状态、类型和 gate;
- Attention 可以只是某种 Relation Eval backend。
---
## 15. 稀疏关系,而不是全连接爆破
Transformer 的典型关系空间:
```text
L × L
```
NGRA-Tensor 的目标:
```text
E << N²
```
即只计算显式 Relation。
但必须承认:
- 稀疏图带来 gather;
- 不规则访问会伤害 GPU;
- 小算子过多会降低吞吐。
因此不能简单逐 Relation 启动 kernel。
---
## 16. Relation Tile / 脑区式分块
后续 GPU backend 应研究:
```text
Semantic Relation Graph
↓
Graph Partition
↓
Relation Tiles
↓
Block-sparse / Dense subgraph execution
```
目标:
> 块内允许不规则访问,块间尽量转成规则张量传输。
示意:
```text
Dense / semi-dense local region
↓
少量跨区 Relation
↓
another dense / semi-dense region
```
类似“脑区”:
- 区内关系较密;
- 区间关系较稀;
- 每个区可以编译成中型 tensor operator;
- 跨区使用显式 Relation。
这可能成为 NGRA-Tensor 与传统 Transformer 之间的重要折中。
---
## 17. 语义稀疏 vs 物理稠密
前端语义可以是:
```text
Sparse Relation Graph
```
GPU backend 可以通过:
```text
node reorder
feature reorder
relation reorder
graph clustering
block detection
tile packing
kernel fusion
shared-memory staging
```
将其转为:
```text
Dense blocks
+
Block-sparse blocks
+
Irregular sparse tails
```
原则:
> 模型的图结构不等于 GPU 必须逐边解释。
---
## 18. Node State 可以是高维 Tensor
NGRA-Tensor 不等于“每个 Node 只有一个 scalar”。
Node 可以持有:
```text
x_i = [f0, f1, ..., fN]
```
但 Relation 不一定读取全部 feature。
因此可能存在两层稀疏:
```text
Topology sparsity
Feature sparsity
```
Relation 可以只对特定 feature/subspace 感兴趣。
例如:
```text
Relation A:
Q features = [1, 2, 7]
K features = [4, 8]
V features = [16, 17, 18]
```
语义上只取这些 feature。
物理执行时 GPU compiler 可以为了带宽一次读入更大的连续 block。
---
## 19. Tensor 观念
后续 NGRA-Tensor 设计不要把 Tensor 只理解为“算子”。
Tensor 可以同时是:
```text
状态
参数
关系
mask
中间结果
数据集合
算子
```
“算子”只是某个 Tensor 在某次 contraction 中扮演的角色。
建议以“语义轴”理解 Tensor,例如:
```text
Node × Feature
Destination × Source
Relation × Feature
Time × Node × Feature
```
优先思考:
- 哪些轴保留;
- 哪些轴被收缩;
- 哪些轴表示关系;
- 哪些轴表示 feature;
- 哪些轴可以 sparse;
- 哪些轴可以 dense。
---
## 20. 训练 / 运行时关系发现
NGRA-Tensor 长期设想允许结构学习。
训练阶段:
```text
短程强关系
↓
词 / Token 局部关系
↓
词组 / 短句
↓
长句 / 语义
↓
知识网络 / 长程弱关系
```
运行时:
当现有网络:
```text
NoActivePath
LowConfidence
HighAmbiguity
Unknown
```
可进入较慢的 Relation Discovery:
```text
Stable Graph
↓
Unknown
↓
Candidate Relation Search
↓
Candidate Graph
↓
验证
↓
可能固化进入 Stable Graph
```
不建议运行时每一步都修改主图。
应区分:
```text
Fast inference time
Slow structural learning time
```
---
# 近期开发优先级
## Phase 1 — ε-NFA Reference Runtime
实现:
```text
EpsilonNfa<TInput>
StateId
Transition
ε-closure
Consume
Accept
```
目标:
- 语义简单;
- 正确性优先;
- 不做复杂性能优化;
- 不绑定 `char`。
## Phase 2 — Pattern → NFA Lowering
从现有 AST / Pattern IR 构造 NFA:
```text
literal
predicate
concat
alt
repeat
```
## Phase 3 — NFA Merge
实现:
```text
multi-pattern merge
PatternId
shared start
accept payload
```
然后测试大量 Pattern 同时运行。
## Phase 4 — Token Stream
将输入切换到:
```text
Token
```
支持:
```text
Token kind
Token value
Token metadata
simple binding
context predicate
```
目标:作为编译器前端 / Token Regex 原型。
## Phase 5 — Hierarchical Automata
允许低层自动机输出高层节点,并再次进入更高层自动机。
## Phase 6 — Weighted Automaton
将:
```text
active : bool
```
推广为:
```text
activation : float
```
先不要急于引入复杂 Tensor。
## Phase 7 — NGRA-Tensor Prototype
研究:
```text
Node state tensor
Typed Relation
Eligibility
Compatibility
Transfer
Blend
Sparse / block-sparse execution
```
QKV/Attention 可作为 baseline Relation operator。
---
# 非目标
近期不要优先投入:
```text
完整 .NET Regex 兼容
极端 Span<char> 优化
复杂 Source Generator matcher
完整 capture rollback
所有 lookaround/backreference
为了 Regex 完整性设计核心 IR
```
这些功能如确实需要,可以:
```text
System.Text.RegularExpressions
```
作为字符串兼容层。
这不是架构退让,而是目标调整。
---
# 一句话定位
NGRA 的新定位:
> **以 NFA 为基础的广义序列模式与分层自动机框架,并逐步推广到加权 Relation Network 与 NGRA-Tensor。**
Text Regex:
> 一个兼容前端 / 参考实现,不再定义 NGRA 的核心架构。
NGRA-Tensor:
> 把 Boolean NFA 的多状态传播推广为显式、稀疏、可加权、可训练、可分块执行的 Relation Network。
+11
View File
@@ -0,0 +1,11 @@
# nFramework Icon
`nFramework-icon.svg` is the icon-only version of the nFramework mark.
Copyright (c) 2026 Nitload.
The artwork in this directory is licensed under the [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0)](https://creativecommons.org/licenses/by-nc-nd/4.0/).
You may copy and redistribute the unmodified artwork for noncommercial purposes when you give appropriate credit and link to the license. This license does not permit distributing adaptations, and it does not grant permission for commercial use. Contact the copyright holder for other permissions.
The repository's root MIT License applies to software, not the artwork in this directory. This copyright license does not grant trademark rights in the Nitload or nFramework names or marks, including the former StgSharp name, or imply endorsement.
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="616" height="104" viewBox="0 0 616 104" role="img" aria-labelledby="title desc">
<title id="title">nFramework</title>
<desc id="desc">Horizontal nFramework logo with the upright punctuation icon and an IBM Plex Mono Medium wordmark.</desc>
<g fill="#59B2DA" transform="translate(13.6 13.6) scale(0.8)"><polygon points="8,8 31,8 31,20 20,20 20,76 31,76 31,88 8,88" /><polygon points="97,8 120,8 120,88 97,88 97,76 108,76 108,20 97,20" /><polygon points="40,16 53,16 53,45 40,45" /><circle cx="80" cy="76" r="8" /></g>
<g fill="#E9EEF5"><path d="M146.384 78.524V39.308H154.668V45.996H155.048Q155.656 44.476 156.606 43.07Q157.556 41.664 158.924 40.638Q160.292 39.612 162.116 39.004Q163.94 38.396 166.372 38.396Q172.3 38.396 175.948 42.272Q179.596 46.148 179.596 53.292V78.524H171.312V54.508Q171.312 45.16 163.18 45.16Q161.584 45.16 160.064 45.578Q158.544 45.996 157.328 46.832Q156.112 47.668 155.39 48.96Q154.668 50.252 154.668 51.924V78.524Z"/><path d="M192.136 78.524V25.476H226.26V32.62H200.648V48.048H223.904V55.192H200.648V78.524Z"/><path d="M236.976 71.836H247.388V45.996H236.976V39.308H255.672V49.72H256.128Q256.66 47.668 257.648 45.768Q258.636 43.868 260.156 42.462Q261.676 41.056 263.88 40.182Q266.084 39.308 269.048 39.308H273.76V47.212H266.008Q261.448 47.212 258.56 49.91Q255.672 52.608 255.672 57.168V71.836H269.884V78.524H236.976Z"/><path d="M314.192 78.524Q310.62 78.524 308.834 76.7Q307.048 74.876 306.668 71.912H306.288Q305.148 75.484 302.146 77.46Q299.144 79.436 294.508 79.436Q288.504 79.436 284.932 76.282Q281.36 73.128 281.36 67.58Q281.36 61.88 285.578 58.878Q289.796 55.876 298.536 55.876H306.06V52.836Q306.06 44.856 297.624 44.856Q293.824 44.856 291.468 46.338Q289.112 47.82 287.516 50.252L282.576 46.224Q284.248 43.032 288.2 40.714Q292.152 38.396 298.384 38.396Q305.832 38.396 310.088 41.968Q314.344 45.54 314.344 52.304V71.988H319.284V78.524ZM296.864 73.356Q300.892 73.356 303.476 71.532Q306.06 69.708 306.06 66.82V61.12H298.688Q289.72 61.12 289.72 66.516V68.036Q289.72 70.696 291.62 72.026Q293.52 73.356 296.864 73.356Z"/><path d="M325.744 78.524V39.308H332.812V44.4H333.192Q334.104 41.892 335.814 40.144Q337.524 38.396 340.716 38.396Q343.832 38.396 345.542 40.106Q347.252 41.816 347.708 44.78H348.012Q349.076 42.12 350.976 40.258Q352.876 38.396 356.448 38.396Q361.008 38.396 362.794 41.588Q364.58 44.78 364.58 50.936V78.524H357.512V52Q357.512 47.896 356.524 46.338Q355.536 44.78 353.332 44.78Q351.356 44.78 350.026 46.11Q348.696 47.44 348.696 50.1V78.524H341.628V52Q341.628 47.896 340.678 46.338Q339.728 44.78 337.524 44.78Q335.472 44.78 334.142 46.11Q332.812 47.44 332.812 50.1V78.524Z"/><path d="M391.636 79.436Q387.152 79.436 383.58 77.992Q380.008 76.548 377.576 73.888Q375.144 71.228 373.852 67.466Q372.56 63.704 372.56 58.992Q372.56 54.28 373.89 50.48Q375.22 46.68 377.614 43.982Q380.008 41.284 383.428 39.84Q386.848 38.396 391.028 38.396Q395.132 38.396 398.476 39.802Q401.82 41.208 404.176 43.792Q406.532 46.376 407.786 49.948Q409.04 53.52 409.04 57.852V60.968H380.92V62.26Q380.92 66.896 383.808 69.822Q386.696 72.748 391.788 72.748Q395.588 72.748 398.286 71.114Q400.984 69.48 402.656 66.744L407.824 71.304Q405.848 74.724 401.782 77.08Q397.716 79.436 391.636 79.436ZM391.028 44.704Q388.824 44.704 386.962 45.464Q385.1 46.224 383.77 47.63Q382.44 49.036 381.68 50.936Q380.92 52.836 380.92 55.116V55.648H400.604V54.888Q400.604 50.252 397.982 47.478Q395.36 44.704 391.028 44.704Z"/><path d="M415.12 39.308H422.188L425.988 72.292H426.672L432.524 39.308H440.58L446.356 72.292H447.04L450.916 39.308H457.68L452.208 78.524H442.024L436.78 48.124H436.096L430.776 78.524H420.592Z"/><path d="M482 79.436Q477.668 79.436 474.21 77.992Q470.752 76.548 468.358 73.888Q465.964 71.228 464.672 67.428Q463.38 63.628 463.38 58.916Q463.38 54.204 464.672 50.404Q465.964 46.604 468.358 43.944Q470.752 41.284 474.21 39.84Q477.668 38.396 482 38.396Q486.332 38.396 489.79 39.84Q493.248 41.284 495.642 43.944Q498.036 46.604 499.328 50.404Q500.62 54.204 500.62 58.916Q500.62 63.628 499.328 67.428Q498.036 71.228 495.642 73.888Q493.248 76.548 489.79 77.992Q486.332 79.436 482 79.436ZM482 72.9Q486.56 72.9 489.258 70.126Q491.956 67.352 491.956 61.88V55.952Q491.956 50.48 489.258 47.706Q486.56 44.932 482 44.932Q477.44 44.932 474.742 47.706Q472.044 50.48 472.044 55.952V61.88Q472.044 67.352 474.742 70.126Q477.44 72.9 482 72.9Z"/><path d="M510.576 71.836H520.988V45.996H510.576V39.308H529.272V49.72H529.728Q530.26 47.668 531.248 45.768Q532.236 43.868 533.756 42.462Q535.276 41.056 537.48 40.182Q539.684 39.308 542.648 39.308H547.36V47.212H539.608Q535.048 47.212 532.16 49.91Q529.272 52.608 529.272 57.168V71.836H543.484V78.524H510.576Z"/><path d="M557.088 22.284H565.372V56.256H565.752L571.908 49.948L582.928 39.308H592.732L576.848 54.736L594.48 78.524H584.524L570.844 59.448L565.372 64.54V78.524H557.088Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="616" height="104" viewBox="0 0 616 104" role="img" aria-labelledby="title desc">
<title id="title">nFramework</title>
<desc id="desc">Horizontal nFramework logo with the upright punctuation icon and an IBM Plex Mono Medium wordmark.</desc>
<g fill="#59B2DA" transform="translate(13.6 13.6) scale(0.8)"><polygon points="8,8 31,8 31,20 20,20 20,76 31,76 31,88 8,88" /><polygon points="97,8 120,8 120,88 97,88 97,76 108,76 108,20 97,20" /><polygon points="40,16 53,16 53,45 40,45" /><circle cx="80" cy="76" r="8" /></g>
<g fill="#14202D"><path d="M146.384 78.524V39.308H154.668V45.996H155.048Q155.656 44.476 156.606 43.07Q157.556 41.664 158.924 40.638Q160.292 39.612 162.116 39.004Q163.94 38.396 166.372 38.396Q172.3 38.396 175.948 42.272Q179.596 46.148 179.596 53.292V78.524H171.312V54.508Q171.312 45.16 163.18 45.16Q161.584 45.16 160.064 45.578Q158.544 45.996 157.328 46.832Q156.112 47.668 155.39 48.96Q154.668 50.252 154.668 51.924V78.524Z"/><path d="M192.136 78.524V25.476H226.26V32.62H200.648V48.048H223.904V55.192H200.648V78.524Z"/><path d="M236.976 71.836H247.388V45.996H236.976V39.308H255.672V49.72H256.128Q256.66 47.668 257.648 45.768Q258.636 43.868 260.156 42.462Q261.676 41.056 263.88 40.182Q266.084 39.308 269.048 39.308H273.76V47.212H266.008Q261.448 47.212 258.56 49.91Q255.672 52.608 255.672 57.168V71.836H269.884V78.524H236.976Z"/><path d="M314.192 78.524Q310.62 78.524 308.834 76.7Q307.048 74.876 306.668 71.912H306.288Q305.148 75.484 302.146 77.46Q299.144 79.436 294.508 79.436Q288.504 79.436 284.932 76.282Q281.36 73.128 281.36 67.58Q281.36 61.88 285.578 58.878Q289.796 55.876 298.536 55.876H306.06V52.836Q306.06 44.856 297.624 44.856Q293.824 44.856 291.468 46.338Q289.112 47.82 287.516 50.252L282.576 46.224Q284.248 43.032 288.2 40.714Q292.152 38.396 298.384 38.396Q305.832 38.396 310.088 41.968Q314.344 45.54 314.344 52.304V71.988H319.284V78.524ZM296.864 73.356Q300.892 73.356 303.476 71.532Q306.06 69.708 306.06 66.82V61.12H298.688Q289.72 61.12 289.72 66.516V68.036Q289.72 70.696 291.62 72.026Q293.52 73.356 296.864 73.356Z"/><path d="M325.744 78.524V39.308H332.812V44.4H333.192Q334.104 41.892 335.814 40.144Q337.524 38.396 340.716 38.396Q343.832 38.396 345.542 40.106Q347.252 41.816 347.708 44.78H348.012Q349.076 42.12 350.976 40.258Q352.876 38.396 356.448 38.396Q361.008 38.396 362.794 41.588Q364.58 44.78 364.58 50.936V78.524H357.512V52Q357.512 47.896 356.524 46.338Q355.536 44.78 353.332 44.78Q351.356 44.78 350.026 46.11Q348.696 47.44 348.696 50.1V78.524H341.628V52Q341.628 47.896 340.678 46.338Q339.728 44.78 337.524 44.78Q335.472 44.78 334.142 46.11Q332.812 47.44 332.812 50.1V78.524Z"/><path d="M391.636 79.436Q387.152 79.436 383.58 77.992Q380.008 76.548 377.576 73.888Q375.144 71.228 373.852 67.466Q372.56 63.704 372.56 58.992Q372.56 54.28 373.89 50.48Q375.22 46.68 377.614 43.982Q380.008 41.284 383.428 39.84Q386.848 38.396 391.028 38.396Q395.132 38.396 398.476 39.802Q401.82 41.208 404.176 43.792Q406.532 46.376 407.786 49.948Q409.04 53.52 409.04 57.852V60.968H380.92V62.26Q380.92 66.896 383.808 69.822Q386.696 72.748 391.788 72.748Q395.588 72.748 398.286 71.114Q400.984 69.48 402.656 66.744L407.824 71.304Q405.848 74.724 401.782 77.08Q397.716 79.436 391.636 79.436ZM391.028 44.704Q388.824 44.704 386.962 45.464Q385.1 46.224 383.77 47.63Q382.44 49.036 381.68 50.936Q380.92 52.836 380.92 55.116V55.648H400.604V54.888Q400.604 50.252 397.982 47.478Q395.36 44.704 391.028 44.704Z"/><path d="M415.12 39.308H422.188L425.988 72.292H426.672L432.524 39.308H440.58L446.356 72.292H447.04L450.916 39.308H457.68L452.208 78.524H442.024L436.78 48.124H436.096L430.776 78.524H420.592Z"/><path d="M482 79.436Q477.668 79.436 474.21 77.992Q470.752 76.548 468.358 73.888Q465.964 71.228 464.672 67.428Q463.38 63.628 463.38 58.916Q463.38 54.204 464.672 50.404Q465.964 46.604 468.358 43.944Q470.752 41.284 474.21 39.84Q477.668 38.396 482 38.396Q486.332 38.396 489.79 39.84Q493.248 41.284 495.642 43.944Q498.036 46.604 499.328 50.404Q500.62 54.204 500.62 58.916Q500.62 63.628 499.328 67.428Q498.036 71.228 495.642 73.888Q493.248 76.548 489.79 77.992Q486.332 79.436 482 79.436ZM482 72.9Q486.56 72.9 489.258 70.126Q491.956 67.352 491.956 61.88V55.952Q491.956 50.48 489.258 47.706Q486.56 44.932 482 44.932Q477.44 44.932 474.742 47.706Q472.044 50.48 472.044 55.952V61.88Q472.044 67.352 474.742 70.126Q477.44 72.9 482 72.9Z"/><path d="M510.576 71.836H520.988V45.996H510.576V39.308H529.272V49.72H529.728Q530.26 47.668 531.248 45.768Q532.236 43.868 533.756 42.462Q535.276 41.056 537.48 40.182Q539.684 39.308 542.648 39.308H547.36V47.212H539.608Q535.048 47.212 532.16 49.91Q529.272 52.608 529.272 57.168V71.836H543.484V78.524H510.576Z"/><path d="M557.088 22.284H565.372V56.256H565.752L571.908 49.948L582.928 39.308H592.732L576.848 54.736L594.48 78.524H584.524L570.844 59.448L565.372 64.54V78.524H557.088Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="104" viewBox="0 0 128 104" role="img" aria-labelledby="title desc">
<title id="title">nFramework icon</title>
<desc id="desc">The geometric nFramework mark, with a diagonal slash suggesting an N.</desc>
<metadata>Copyright (c) 2026 Nitload. Licensed under CC BY-NC-ND 4.0: https://creativecommons.org/licenses/by-nc-nd/4.0/</metadata>
<g fill="#59B2DA" transform="translate(13.6 13.6) scale(0.8)">
<polygon points="8,8 31,8 31,20 20,20 20,76 31,76 31,88 8,88" />
<polygon points="97,8 120,8 120,88 97,88 97,76 108,76 108,20 97,20" />
<polygon points="40,16 53,16 53,45 40,45" />
<circle cx="80" cy="76" r="8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 709 B

@@ -6,14 +6,14 @@
// -----------------------------------------------------------------------------
using BenchmarkDotNet.Attributes;
using StgSharp.HighPerformance.Memory;
using Nitload.Common.HighPerformance.Memory;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Tlsf = StgSharp.HighPerformance.Memory.TwoLayerSegregatedFitAllocator;
using Tlsf = Nitload.Common.HighPerformance.Memory.TwoLayerSegregatedFitAllocator;
namespace StgSharp.Benchmark
namespace Nitload.Benchmark
{
[MemoryDiagnoser]
public class TlsfAllocatorBench
@@ -10,12 +10,12 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StgSharp.Common\StgSharp.Common.csproj" Properties="DisablePostBuildCopy=true" />
<ProjectReference Include="..\Nitload.Common\Nitload.Common.csproj" Properties="DisablePostBuildCopy=true" />
</ItemGroup>
<ItemGroup>
<NativeArtifacts Include="$(SolutionDir)build\**\StgSharp.Native*.dll" />
<NativeArtifacts Include="$(SolutionDir)build\**\StgSharp.Native*.pdb" />
<NativeArtifacts Include="$(SolutionDir)build\**\Nitload.Native*.dll" />
<NativeArtifacts Include="$(SolutionDir)build\**\Nitload.Native*.pdb" />
</ItemGroup>
<Target Name="CopyNativeArtifacts" AfterTargets="Build" Condition="'$(DisableNativeCopy)'!='true'">
@@ -9,16 +9,23 @@ using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using StgSharp.Benchmark;
using Nitload.Benchmark;
if (args.Length > 0 && args[0] == "--disasm-swiss")
{
IConfig disassemblyConfig = ManualConfig.Create(DefaultConfig.Instance)
.AddLogger(ConsoleLogger.Default)
.AddJob(Job.ShortRun.WithWarmupCount(6));
BenchmarkRunner.Run<SwissX86DisassemblyBench>(disassemblyConfig);
return;
}
IConfig config = ManualConfig.Create(DefaultConfig.Instance)
.AddLogger(ConsoleLogger.Default)
.AddJob(Job.Default
.WithToolchain(InProcessEmitToolchain.Instance)
.WithLaunchCount(1)
.WithWarmupCount(6)
.WithIterationCount(15)
.WithInvocationCount(3)
.WithUnrollFactor(1));
_ = BenchmarkRunner.Run<TlsfAllocatorBench>(config);
BenchmarkSwitcher.FromAssembly(typeof(TlsfAllocatorBench).Assembly).Run(args, config);
+306
View File
@@ -0,0 +1,306 @@
// -----------------------------------------------------------------------------
// file="SwissX86Bench"
// Project: nFramework
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Nitload.Common.HighPerformance.Memory;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Threading;
using System.Threading.Tasks;
namespace Nitload.Benchmark
{
[MemoryDiagnoser]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public unsafe class SwissX86Bench
{
private const int QueryCount = 1 << 21;
private const int ConcurrentQueryCount = QueryCount * 6;
private const int MaximumResidentCount = 8192;
private byte* _buffer;
private L4.SwissTable _swiss;
private Dictionary<L4.AddressSource, ulong> _dictionary = null!;
private ConcurrentDictionary<L4.AddressSource, ulong> _concurrentDictionary = null!;
private L4.AddressSource[] _hitQueries = [];
private L4.AddressSource[] _missQueries = [];
private L4.AddressSource[] _writerQueries = [];
private ulong[] _writerValues = [];
private byte* _readBuffer;
private byte* _writeBuffer;
private ParallelOptions _parallelOptions = null!;
private Func<ulong> _parallelLocalInit = null!;
private Func<int, ParallelLoopState, ulong, ulong> _swissParallelBody = null!;
private Func<int, ParallelLoopState, ulong, ulong> _concurrentDictionaryParallelBody = null!;
private Action<ulong> _parallelLocalFinally = null!;
private long _parallelChecksum;
[Params(25, 50, 100)]
public int LoadPercent { get; set; }
private int ResidentCount => MaximumResidentCount * LoadPercent / 100;
[GlobalSetup]
public void Setup()
{
if (!Sse42.X64.IsSupported)
throw new PlatformNotSupportedException("The Swiss X86 benchmark requires x64 SSE4.2.");
_buffer = (byte*)NativeMemory.AlignedAlloc((nuint)L4.BufferSize, 64);
if (_buffer == null)
throw new OutOfMemoryException("Could not allocate Swiss benchmark tables.");
_swiss = L4.SwissTable.Create(_buffer);
_dictionary = new Dictionary<L4.AddressSource, ulong>(ResidentCount);
_concurrentDictionary = new ConcurrentDictionary<L4.AddressSource, ulong>(
Math.Max(1, Math.Min(Environment.ProcessorCount, 8)),
ResidentCount);
L4.AddressSource[] residentKeys = new L4.AddressSource[ResidentCount];
for (int i = 0; i < ResidentCount; i++)
{
L4.AddressSource key = MakeKey(i);
ulong value = (ulong)i + 1;
residentKeys[i] = key;
if (!_swiss.TryAddOrSet(key, value))
throw new InvalidOperationException($"Swiss insertion failed while seeding key {i}.");
_dictionary.Add(key, value);
_concurrentDictionary.TryAdd(key, value);
}
_swiss.SwapBuffers();
for (int i = 0; i < ResidentCount; i++)
{
if (!_swiss.TryAddOrSet(residentKeys[i], (ulong)i + 1))
throw new InvalidOperationException($"Swiss writer-buffer seeding failed for key {i}.");
}
_readBuffer = _swiss.ReadBufferForLockFreeBenchmark;
_writeBuffer = _swiss.WriteBufferForLockFreeBenchmark;
_hitQueries = new L4.AddressSource[QueryCount];
_missQueries = new L4.AddressSource[QueryCount];
_writerQueries = new L4.AddressSource[QueryCount];
_writerValues = new ulong[QueryCount];
Random random = new(0x51_15_2026);
for (int i = 0; i < QueryCount; i++)
{
_hitQueries[i] = residentKeys[random.Next(ResidentCount)];
_missQueries[i] = MakeKey(MaximumResidentCount + i);
_writerQueries[i] = residentKeys[random.Next(ResidentCount)];
_writerValues[i] = (ulong)i + 0x10000;
}
_parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = Math.Max(2, Math.Min(Environment.ProcessorCount, 8))
};
_parallelLocalInit = static() => 0UL;
_swissParallelBody = ReadSwissParallel;
_concurrentDictionaryParallelBody = ReadConcurrentDictionaryParallel;
_parallelLocalFinally = MergeParallelChecksum;
ValidateLookupResults();
}
[GlobalCleanup]
public void Cleanup()
{
_swiss.Dispose();
NativeMemory.AlignedFree(_buffer);
_buffer = null;
}
[Benchmark(Baseline = true, OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Hit lookup")]
public ulong DictionaryHitLookup()
{
ulong checksum = 0;
for (int i = 0; i < QueryCount; i++)
checksum += _dictionary[_hitQueries[i]];
return checksum;
}
[Benchmark(OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Hit lookup")]
public ulong SwissLockFreeHitLookup()
{
ulong checksum = 0;
for (int i = 0; i < QueryCount; i++)
{
if (!L4.SwissReaderX86.TryGet(_readBuffer, _hitQueries[i], out ulong value))
throw new InvalidOperationException("Swiss table missed a seeded hit key.");
checksum += value;
}
return checksum;
}
[Benchmark(Baseline = true, OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Miss lookup")]
public int DictionaryMissLookup()
{
int misses = 0;
for (int i = 0; i < QueryCount; i++)
misses += _dictionary.TryGetValue(_missQueries[i], out _) ? 0 : 1;
return misses;
}
[Benchmark(OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Miss lookup")]
public int SwissLockFreeMissLookup()
{
int misses = 0;
for (int i = 0; i < QueryCount; i++)
misses += L4.SwissReaderX86.TryGet(_readBuffer, _missQueries[i], out _) ? 0 : 1;
return misses;
}
[Benchmark(Baseline = true, OperationsPerInvoke = ConcurrentQueryCount)]
[BenchmarkCategory("Concurrent hit lookup")]
public ulong ConcurrentDictionaryConcurrentHitLookup()
{
Interlocked.Exchange(ref _parallelChecksum, 0);
Parallel.For(
0,
ConcurrentQueryCount,
_parallelOptions,
_parallelLocalInit,
_concurrentDictionaryParallelBody,
_parallelLocalFinally);
return unchecked((ulong)Interlocked.Read(ref _parallelChecksum));
}
[Benchmark(OperationsPerInvoke = ConcurrentQueryCount)]
[BenchmarkCategory("Concurrent hit lookup")]
public ulong SwissLockFreeConcurrentHitLookup()
{
Interlocked.Exchange(ref _parallelChecksum, 0);
Parallel.For(
0,
ConcurrentQueryCount,
_parallelOptions,
_parallelLocalInit,
_swissParallelBody,
_parallelLocalFinally);
return unchecked((ulong)Interlocked.Read(ref _parallelChecksum));
}
[Benchmark(Baseline = true, OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Upsert existing")]
public ulong DictionaryUpsertExisting()
{
for (int i = 0; i < QueryCount; i++)
_dictionary[_writerQueries[i]] = _writerValues[i];
return _dictionary[_writerQueries[^1]];
}
[Benchmark(OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Upsert existing")]
public ulong SwissUpsertExisting()
{
ulong failures = 0;
for (int i = 0; i < QueryCount; i++)
failures += L4.SwissWriterX86.TryAddOrSet(_writeBuffer, _writerQueries[i], _writerValues[i]) ? 0UL : 1UL;
if (failures != 0 || !L4.SwissReaderX86.TryGet(_writeBuffer, _writerQueries[^1], out ulong value))
throw new InvalidOperationException("Swiss upsert failed to preserve the final value.");
return value;
}
[Benchmark(Baseline = true, OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Remove and reinsert")]
public ulong DictionaryRemoveAndReinsert()
{
ulong checksum = 0;
for (int i = 0; i < QueryCount; i++)
{
L4.AddressSource key = _writerQueries[i];
ulong value = _dictionary[key];
_dictionary.Remove(key);
_dictionary.Add(key, value + 1);
checksum += value;
}
return checksum;
}
[Benchmark(OperationsPerInvoke = QueryCount)]
[BenchmarkCategory("Remove and reinsert")]
public ulong SwissRemoveAndReinsert()
{
ulong checksum = 0;
for (int i = 0; i < QueryCount; i++)
{
L4.AddressSource key = _writerQueries[i];
if (!L4.SwissWriterX86.TryRemove(_writeBuffer, key, out ulong value) ||
!L4.SwissWriterX86.TryAddOrSet(_writeBuffer, key, value + 1)) {
throw new InvalidOperationException("Swiss remove/reinsert failed.");
}
checksum += value;
}
return checksum;
}
private static L4.AddressSource MakeKey(int index)
{
return new L4.AddressSource
{
Address = 0x1000_0000UL + ((ulong)index * 64UL),
Description = ((ulong)index * 0x9e37_79b9UL) ^ 0xd1b5_4a32_d192_ed03UL
};
}
private ulong ReadSwissParallel(int index, ParallelLoopState state, ulong checksum)
{
int queryIndex = index & (QueryCount - 1);
if (!L4.SwissReaderX86.TryGet(_readBuffer, _hitQueries[queryIndex], out ulong value))
throw new InvalidOperationException("Swiss table missed a seeded concurrent hit key.");
return checksum + value;
}
private ulong ReadConcurrentDictionaryParallel(int index, ParallelLoopState state, ulong checksum)
{
int queryIndex = index & (QueryCount - 1);
if (!_concurrentDictionary.TryGetValue(_hitQueries[queryIndex], out ulong value))
throw new InvalidOperationException("ConcurrentDictionary missed a seeded hit key.");
return checksum + value;
}
private void MergeParallelChecksum(ulong checksum)
{
_ = Interlocked.Add(ref _parallelChecksum, unchecked((long)checksum));
}
private void ValidateLookupResults()
{
for (int i = 0; i < _hitQueries.Length; i++)
{
bool dictionaryFound = _dictionary.TryGetValue(_hitQueries[i], out ulong dictionaryValue);
bool swissFound = L4.SwissReaderX86.TryGet(_readBuffer, _hitQueries[i], out ulong swissValue);
if (!dictionaryFound || !swissFound || dictionaryValue != swissValue)
throw new InvalidOperationException("Swiss and Dictionary hit results differ.");
if (!_concurrentDictionary.TryGetValue(_hitQueries[i], out ulong concurrentValue) ||
concurrentValue != dictionaryValue)
throw new InvalidOperationException("ConcurrentDictionary and Dictionary hit results differ.");
}
for (int i = 0; i < _missQueries.Length; i++)
{
bool dictionaryFound = _dictionary.TryGetValue(_missQueries[i], out _);
bool swissFound = L4.SwissReaderX86.TryGet(_readBuffer, _missQueries[i], out _);
if (dictionaryFound || swissFound)
throw new InvalidOperationException("Swiss and Dictionary miss results differ.");
}
}
}
}
@@ -0,0 +1,135 @@
// -----------------------------------------------------------------------------
// file="SwissX86DisassemblyBench"
// Project: nFramework
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Nitload.Common.HighPerformance.Memory;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
namespace Nitload.Benchmark
{
[DisassemblyDiagnoser(maxDepth: 3, exportHtml: true)]
public unsafe class SwissX86DisassemblyBench
{
private const int ResidentCount = 8192;
private byte* _buffer;
private L4.SwissTable _table;
private byte* _readBuffer;
private byte* _writeBuffer;
private L4.AddressSource _hitKey;
private L4.AddressSource _missKey;
private ulong _value;
[GlobalSetup]
public void Setup()
{
if (!Sse42.X64.IsSupported)
throw new PlatformNotSupportedException("The Swiss X86 disassembly probe requires x64 SSE4.2.");
_buffer = (byte*)NativeMemory.AlignedAlloc((nuint)L4.BufferSize, 64);
if (_buffer == null)
throw new OutOfMemoryException("Could not allocate Swiss disassembly buffers.");
_table = L4.SwissTable.Create(_buffer);
for (int index = 0; index < ResidentCount; index++)
{
if (!L4.SwissWriterX86.TryAddOrSet(_table.WriteBufferForLockFreeBenchmark,
MakeKey(index), (ulong)index + 1))
throw new InvalidOperationException($"Failed to seed Swiss key {index}.");
}
_table.SwapBuffers();
_readBuffer = _table.ReadBufferForLockFreeBenchmark;
_writeBuffer = _table.WriteBufferForLockFreeBenchmark;
for (int index = 0; index < ResidentCount; index++)
{
if (!L4.SwissWriterX86.TryAddOrSet(_writeBuffer, MakeKey(index), (ulong)index + 1))
throw new InvalidOperationException($"Failed to seed inactive Swiss key {index}.");
}
_hitKey = MakeKey(ResidentCount / 2);
_missKey = MakeKey(ResidentCount + 1);
_value = 0x1234_5678;
if (!L4.SwissReaderX86.TryGet(_readBuffer, _hitKey, out _) ||
L4.SwissReaderX86.TryGet(_readBuffer, _missKey, out _))
throw new InvalidOperationException("Swiss reader setup validation failed.");
}
[GlobalCleanup]
public void Cleanup()
{
_table.Dispose();
NativeMemory.AlignedFree(_buffer);
_buffer = null;
}
[Benchmark]
public bool RawReaderHit()
{
return L4.SwissReaderX86.TryGet(_readBuffer, _hitKey, out _value);
}
[Benchmark]
public bool RawReaderMiss()
{
return L4.SwissReaderX86.TryGet(_readBuffer, _missKey, out _value);
}
[Benchmark]
public bool RawWriterUpsertExisting()
{
return L4.SwissWriterX86.TryAddOrSet(_writeBuffer, _hitKey, _value);
}
[Benchmark]
public bool RawWriterRemoveAndReinsert()
{
return L4.SwissWriterX86.TryRemove(_writeBuffer, _hitKey, out ulong previousValue) &&
L4.SwissWriterX86.TryAddOrSet(_writeBuffer, _hitKey, previousValue);
}
[Benchmark]
public bool TableReaderHit()
{
return _table.TryGet(_hitKey, out _value);
}
[Benchmark]
public bool TableReaderMiss()
{
return _table.TryGet(_missKey, out _value);
}
[Benchmark]
public bool TableWriterUpsertExisting()
{
return _table.TryAddOrSet(_hitKey, _value);
}
[Benchmark]
public bool TableWriterRemoveAndReinsert()
{
return _table.TryRemove(_hitKey, out ulong previousValue) &&
_table.TryAddOrSet(_hitKey, previousValue);
}
private static L4.AddressSource MakeKey(int index)
{
return new L4.AddressSource
{
Address = 0x1000_0000UL + ((ulong)index * 64UL),
Description = ((ulong)index * 0x9e37_79b9UL) ^ 0xd1b5_4a32_d192_ed03UL
};
}
}
}
@@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public sealed class BidirectionalDictionary<TFirst, TSecond> : IBidirectionalDictionary<TFirst, TSecond>
where TFirst : notnull
@@ -10,7 +10,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public sealed class CapacityFixedBag<T> : IEnumerable<T>
{
@@ -10,7 +10,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public static class CapacityFixedListBuilder
{
@@ -6,12 +6,14 @@
// -----------------------------------------------------------------------------
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Threading;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
[CollectionBuilder(typeof(CapacityFixedStackBuilder), nameof(CapacityFixedStackBuilder.Create))]
public class CapacityFixedStack<T> : IEnumerable<T>
@@ -137,6 +139,18 @@ namespace StgSharp.Collections
return true;
}
internal void FillRange(
SpanAction<T, nuint> filling,
nuint buffer
)
{
if (filling is not null)
{
filling(_values.AsSpan()[_index..], buffer);
_index = _values.Length;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span<T> GetValueLayout()
{
@@ -163,4 +177,4 @@ namespace StgSharp.Collections
}
}
}
}
@@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
/// <summary>
/// Lock-free, fixed-capacity stack implemented on top of an array using atomic index
@@ -11,7 +11,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public class ConcurrentFlexibleArray<T>(
int capacity = 4
@@ -7,7 +7,7 @@
using System.Collections.Generic;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public interface IBidirectionalDictionary<TFirst, TSecond> : IDictionary<TFirst, TSecond>
{
@@ -9,7 +9,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public static class LinearBagBuilder
{
@@ -14,7 +14,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
/// <summary>
/// Part of a collection. This contains a direct reference to _origin collection, so all
@@ -9,7 +9,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public class RandomAccessibleStack<T> : IEnumerable<T>
{
@@ -12,7 +12,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
/// <summary>
/// Zero-overhead view over a contiguous region of unmanaged memory. Does not own the memory —
@@ -11,7 +11,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Collections
namespace Nitload.Common.Collections
{
public unsafe class UnmanagedStack<T>(
T* address,
@@ -16,7 +16,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace StgSharp
namespace Nitload
{
public static class Dialogue
{
@@ -92,7 +92,7 @@ namespace StgSharp
}
string consoleRoute = Path.Combine(
path, @"StgSharp.TerminalDialogue", $@"StgSharp.TerminalDialogue{exeName}");
path, @"Nitload.TerminalDialogue", $@"Nitload.TerminalDialogue{exeName}");
_dialogueProcessInfo = new ProcessStartInfo
{
@@ -11,7 +11,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp
namespace Nitload
{
public class UniformEmptyReferenceException : Exception
{
@@ -14,15 +14,13 @@ using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp
namespace Nitload
{
public static unsafe partial class World
{
internal const int ssdSegmentLength = 16;
private static byte[] _currentHash = [];
private static bool _inited;
public static byte[] CurrentAssemblyHash
@@ -32,12 +30,10 @@ namespace StgSharp
if (_currentHash == null)
{
string route = Assembly.GetExecutingAssembly().Location;
string time = DateTime.UtcNow.ToString();
using (SHA256 sh = SHA256.Create())
{
byte[] bytes = Encoding.UTF8.GetBytes(route);
_currentHash = sh.ComputeHash(bytes);
}
// string time = DateTime.UtcNow.ToString();
byte[] bytes = Encoding.UTF8.GetBytes(route);
_currentHash = SHA256.HashData(bytes);
}
return _currentHash;
}
@@ -52,6 +48,8 @@ namespace StgSharp
internal set => _mainThreadID = value;
}
private static byte[] _currentHash { get; set; } = [];
private static int _mainThreadID { get; set; } = -1;
public static class GlobalSetting
@@ -62,12 +60,10 @@ namespace StgSharp
public static bool VSyncActivated
{
get => vsyncActivated;
set =>
// GraphicFramework.glfwSwapInterval(value ? 1 : 0);
vsyncActivated = value;
set => vsyncActivated = value; // GraphicFramework.glfwSwapInterval(value ? 1 : 0);
}
}
}//-------------------------------------- End of Class ---------------------------------------//
}
}
@@ -9,7 +9,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
namespace StgSharp
namespace Nitload
{
/// <summary>
/// Represents a statically activated module that can be registered and initialized as part of
@@ -27,11 +27,11 @@ namespace StgSharp
/// Performs all initialization work for this module using the provided profile.
/// </summary>
/// <param name="profile">
/// The configuration profile supplied at registration time. Use <see
/// cref="IModuleInitializeProfile.Empty" /> if no configuration is required.
/// The configuration profile supplied at registration time. Use <see ///
/// cref="IModuleInitializeConfiguration.Empty" /> if no configuration is required.
/// </param>
void InitializeModule(
IModuleInitializeProfile profile
IModuleInitializeConfiguration profile
);
/// <summary>
@@ -46,15 +46,15 @@ namespace StgSharp
/// Marker interface for module initialization configuration. Implementations are plain data
/// carriers; no static factory is required.
/// </summary>
public interface IModuleInitializeProfile
public interface IModuleInitializeConfiguration
{
/// <summary>
/// A shared, allocation-free empty profile for modules that require no configuration.
/// </summary>
static readonly IModuleInitializeProfile Empty = EmptyModuleInitializeProfile.Instance;
static readonly IModuleInitializeConfiguration Empty = EmptyModuleInitializeProfile.Instance;
private sealed class EmptyModuleInitializeProfile : IModuleInitializeProfile
private sealed class EmptyModuleInitializeProfile : IModuleInitializeConfiguration
{
public static readonly EmptyModuleInitializeProfile Instance = new();
@@ -66,8 +66,8 @@ namespace StgSharp
}
/// <summary>
/// Pairs an <see cref="IStaticModule" /> with its <see cref="IModuleInitializeProfile" />,
/// ensuring the two are never stored or iterated independently.
/// Pairs an <see cref="IStaticModule" /> with its <see cref="IModuleInitializeConfiguration"
/// />, ensuring the two are never stored or iterated independently.
/// </summary>
/// <remarks>
/// Using a <see langword="readonly struct" /> avoids the boxing overhead that would occur if
@@ -77,20 +77,20 @@ namespace StgSharp
{
/// <summary>
/// Initializes a new <see cref="ModuleDescriptor" /> using <see
/// cref="IModuleInitializeProfile.Empty" /> as the profile.
/// Initializes a new <see cref="ModuleDescriptor" /> using <see ///
/// cref="IModuleInitializeConfiguration.Empty" /> as the profile.
/// </summary>
public ModuleDescriptor(
IStaticModule module
)
: this(module, IModuleInitializeProfile.Empty) { }
: this(module, IModuleInitializeConfiguration.Empty) { }
/// <summary>
/// Initializes a new <see cref="ModuleDescriptor" /> with an explicit profile.
/// </summary>
public ModuleDescriptor(
IStaticModule module,
IModuleInitializeProfile profile
IModuleInitializeConfiguration profile
)
{
Module = module;
@@ -100,7 +100,7 @@ namespace StgSharp
/// <summary>
/// Gets the configuration profile associated with the module.
/// </summary>
public IModuleInitializeProfile Profile { get; }
public IModuleInitializeConfiguration Profile { get; }
/// <summary>
/// Gets the module to be initialized.
@@ -127,8 +127,8 @@ namespace StgSharp
/// <summary>
/// An ordered collection of modules to be initialized during application startup and
/// uninitialized on shutdown. Supports three registration strategies so that the <c> new()
/// </c> generic constraint is never a hard requirement.
/// uninitialized on shutdown. Supports three registration strategies so that the <c>new()
/// ///</c> generic constraint is never a hard requirement.
/// </summary>
public sealed class ModuleToInitializeCollection : IEnumerable<IStaticModule>, IStaticModule
{
@@ -136,10 +136,11 @@ namespace StgSharp
private readonly List<ModuleDescriptor> _descriptors = [];
internal ModuleToInitializeCollection(
World.Initializer i
World.Initializer init,
World.InitializeConfiguration config
)
{
_descriptors.Add(new ModuleDescriptor(i));
_descriptors.Add(new ModuleDescriptor(init, config));
}
// ── IEnumerable<IStaticModule> ───────────────────────────────────────────
@@ -189,11 +190,11 @@ namespace StgSharp
/// public parameterless constructor.
/// </typeparam>
/// <param name="profile">
/// Optional initialization profile. Defaults to <see
/// cref="IModuleInitializeProfile.Empty" /> when <see langword="null" />.
/// Optional initialization profile. Defaults to <see ///
/// cref="IModuleInitializeConfiguration.Empty" /> when <see langword="null" />.
/// </param>
public ModuleToInitializeCollection UseModule<T>(
IModuleInitializeProfile? profile = null
IModuleInitializeConfiguration? profile = null
) where T : IStaticModule, new()
{
return UseModule(static() => new T(), profile);
@@ -206,17 +207,17 @@ namespace StgSharp
/// </summary>
/// <param name="factory">
/// A delegate that constructs and returns the module instance. The delegate is invoked
/// once, immediately at registration time. If <paramref name="factory" /> is <see
/// once, immediately at registration time. If <paramref name="factory" /> is <see ///
/// langword="null" /> or returns <see langword="null" />, the call is silently ignored
/// and the collection is returned unchanged.
/// </param>
/// <param name="profile">
/// Optional initialization profile. Defaults to <see
/// cref="IModuleInitializeProfile.Empty" /> when <see langword="null" />.
/// Optional initialization profile. Defaults to <see ///
/// cref="IModuleInitializeConfiguration.Empty" /> when <see langword="null" />.
/// </param>
public ModuleToInitializeCollection UseModule(
Func<IStaticModule> factory,
IModuleInitializeProfile? profile = null
IModuleInitializeConfiguration? profile = null
)
{
if (factory is null) {
@@ -228,7 +229,7 @@ namespace StgSharp
}
_descriptors.Add(new ModuleDescriptor(
mod,
profile ?? IModuleInitializeProfile.Empty));
profile ?? IModuleInitializeConfiguration.Empty));
return this;
}
@@ -240,17 +241,17 @@ namespace StgSharp
/// The module instance to register.
/// </param>
/// <param name="profile">
/// Optional initialization profile. Defaults to <see
/// cref="IModuleInitializeProfile.Empty" /> when <see langword="null" />.
/// Optional initialization profile. Defaults to <see ///
/// cref="IModuleInitializeConfiguration.Empty" /> when <see langword="null" />.
/// </param>
public ModuleToInitializeCollection UseModule(
IStaticModule module,
IModuleInitializeProfile? profile = null
IModuleInitializeConfiguration? profile = null
)
{
_descriptors.Add(new ModuleDescriptor(
module,
profile ?? IModuleInitializeProfile.Empty));
profile ?? IModuleInitializeConfiguration.Empty));
return this;
}
@@ -263,12 +264,12 @@ namespace StgSharp
// ── IStaticModule explicit implementation ────────────────────────────────
/// <summary>
/// Explicit <see cref="IStaticModule" /> implementation. Delegates to <see
/// Explicit <see cref="IStaticModule" /> implementation. Delegates to <see ///
/// cref="InitializeModule()" />, ignoring the supplied profile because each contained module uses
/// its own registered profile.
/// </summary>
void IStaticModule.InitializeModule(
IModuleInitializeProfile profile
IModuleInitializeConfiguration profile
)
{
InitializeModule();
@@ -278,4 +279,4 @@ namespace StgSharp
string IStaticModule.ModuleName => "Module Initializer";
}
}
}
@@ -5,14 +5,15 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance;
using StgSharp.HighPerformance.ProcessorAbstraction;
using Nitload.Common.HighPerformance;
using Nitload.Common.HighPerformance.ProcessorAbstraction;
using Nitload.Common.Timing;
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace StgSharp
namespace Nitload
{
internal enum GraphicAPI : byte
{
@@ -36,9 +37,11 @@ namespace StgSharp
private static uint markCount = 0;
internal static GraphicAPI API = default;
public static ModuleToInitializeCollection Initialize()
public static ModuleToInitializeCollection Initialize(
InitializeConfiguration config
)
{
return new ModuleToInitializeCollection(new Initializer());
return new ModuleToInitializeCollection(new Initializer(), config);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -177,10 +180,12 @@ namespace StgSharp
}
}
internal static unsafe void InternalInitialize()
internal static unsafe void InternalInitialize(
InitializeConfiguration config
)
{
// Initialize time provider and start providing time
MainTimeProvider.StartProvidingTime();
MainTimeProvider.StartProvidingTime(config.TimingConfig);
DefaultLog.InternalAppendLog("\n\n\n");
DefaultLog.InternalWriteLog(
$"Program {Assembly.GetEntryAssembly()!.FullName} on {Environment.MachineName} Started.", LogType.Info);
@@ -197,16 +202,26 @@ namespace StgSharp
/**/
}
public class InitializeConfiguration : IModuleInitializeConfiguration
{
public TimeSourceStartupConfiguration TimingConfig { get; set; }
}
internal class Initializer : IStaticModule
{
public string ModuleName => "StgSharpCore";
public void InitializeModule(
IModuleInitializeProfile profile
IModuleInitializeConfiguration config
)
{
World.InternalInitialize();
if (config is not InitializeConfiguration world_init) {
throw new InvalidCastException();
}
World.InternalInitialize(world_init);
}
public void UninitializeModule()
@@ -5,14 +5,15 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
global using StgSharp.Internal;
global using Nitload.Common.Internal;
global using System;
global using System.Runtime.CompilerServices;
global using System.Threading;
[assembly: InternalsVisibleTo("StgSharp.HighPerformance")]
[assembly: InternalsVisibleTo("StgSharp.RegularAnalysis")]
[assembly: InternalsVisibleTo("StgSharp.Graphics")]
[assembly: InternalsVisibleTo("StgSharp.Mathematics")]
[assembly: InternalsVisibleTo("Nitload.Common.HighPerformance")]
[assembly: InternalsVisibleTo("Nitload.RegularAnalysis")]
[assembly: InternalsVisibleTo("Nitload.Graphics")]
[assembly: InternalsVisibleTo("Nitload.Mathematics")]
[assembly: InternalsVisibleTo("Nitload.Benchmark")]
[assembly: InternalsVisibleTo("StgSharpDebug")]
@@ -11,7 +11,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance
namespace Nitload.Common.HighPerformance
{
public class IdMultiplexer { }
}
@@ -6,10 +6,11 @@
// -----------------------------------------------------------------------------
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
[CollectionBuilder(typeof(BufferStackBuilder), nameof(BufferStackBuilder.Create))]
public unsafe class BufferStack<T> : IDisposable where T : unmanaged
@@ -92,6 +93,47 @@ namespace StgSharp.HighPerformance.Memory
return true;
}
/// <summary>
/// Fills the stack in place via a caller-supplied callback, mirroring <c>
/// CapacityFixedStack&lt;T&gt;.FillRange </c>.
/// </summary>
/// <remarks>
/// Preferred over building a temporary span and handing it to <see
/// cref="BufferStackBuilder.Create{T}(ReadOnlySpan{T})" />: <list type="bullet"> ///
/// <item> The caller no longer needs a scratch buffer at all — the /// callback writes
/// straight into this stack's native storage, so there /// is no <c> stackalloc </c> to
/// size and no copy afterwards. </item> /// <item><c> Span&lt;T&gt; </c> does not
/// implicitly convert to /// <c> ReadOnlySpan&lt;T&gt; </c> in a way that participates in
/// generic /// type inference before C# 14, so <c> Create(span) </c> fails with ///
/// CS0411 on older compilers. Passing the element type through the /// enclosing instance
/// sidesteps inference entirely. </item> ///</list>
/// </remarks>
internal unsafe void FillRange(
SpanAction<T, nuint> filling,
nuint buffer
)
{
if (filling is null) {
return;
}
Header* header = (Header*)_buffer;
T* dataStart = (T*)(_buffer + sizeof(Header));
// Hand out only the unused tail, matching CapacityFixedStack:
// filling is an append, not a rewrite of live entries.
int start = (int)header->Top;
int room = (int)header->Capacity - start;
if (room <= 0) {
return;
}
filling(new Span<T>(dataStart + start, room), buffer);
header->Top = header->Capacity;
_count = header->Capacity;
}
// Internal method for efficient bulk initialization
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void InitializeFromSpan(
@@ -146,4 +188,4 @@ namespace StgSharp.HighPerformance.Memory
}
}
}
}
@@ -5,15 +5,16 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Threading;
using Nitload.Threading;
using System;
using System.Buffers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
public static class BufferStackBuilder
{
@@ -253,6 +254,49 @@ namespace StgSharp.HighPerformance.Memory
}
}
/// <summary>
/// Fills the stack in place via a caller-supplied callback, mirroring
/// <c>CapacityFixedStack&lt;T&gt;.FillRange</c>.
/// </summary>
/// <remarks>
/// Intended for construction only, and therefore takes no lock — the
/// instance is not yet published to other threads at that point. Do not
/// call it on a stack that is already in concurrent use.
/// <para>
/// Preferred over building a temporary span and handing it to
/// <see cref="BufferStackBuilder.CreateConcurrent{T}(ReadOnlySpan{T})"/>
/// for the same two reasons as the non-concurrent variant: the callback
/// writes straight into native storage (no scratch buffer, no copy),
/// and passing the element type through the enclosing instance avoids
/// the <c>Span</c>-to-<c>ReadOnlySpan</c> inference gap that yields
/// CS0411 before C# 14.
/// </para>
/// </remarks>
internal unsafe void FillRange(
SpanAction<T, nuint> filling,
nuint buffer
)
{
if (filling is null) {
return;
}
Header* header = (Header*)_buffer;
T* dataStart = (T*)(_buffer + sizeof(Header));
// Append into the unused tail only, matching CapacityFixedStack.
int start = header->Top;
int room = header->Capacity - start;
if (room <= 0) {
return;
}
filling(new Span<T>(dataStart + start, room), buffer);
header->Top = header->Capacity;
Volatile.Write(ref _count, header->Capacity);
}
// Internal method for efficient bulk initialization
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void InitializeFromSpan(
@@ -5,10 +5,9 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance.ProcessorAbstraction;
using System.Runtime.InteropServices;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
public partial class L4
{
@@ -23,14 +22,12 @@ namespace StgSharp.HighPerformance.Memory
}
[StructLayout(LayoutKind.Explicit, Size = 32, Pack = 32)]
private unsafe struct CacheLineMetadata
private struct CacheLineMetadata
{
internal const uint MaxBufferSize = 0xFFFFFFFF - 64U;
[FieldOffset(0)] private readonly M256 FillingMask;
[FieldOffset(24)] internal nuint Origin;
[FieldOffset(16)] internal AllocationState State;
public static uint SizeOf { get; } = (uint)Unsafe.SizeOf<CacheLineMetadata>();
@@ -48,33 +45,25 @@ namespace StgSharp.HighPerformance.Memory
[FieldOffset(8)] public uint PositionMask;
[FieldOffset(12)] public uint SizeMask;
#endregion
#region mask
[FieldOffset(16)] internal AllocationState State;
[FieldOffset(17)] internal byte ReversedProfile;
[FieldOffset(18)] internal ushort CustomizeProfile;
[FieldOffset(20)] internal int RefCount;
#endregion
}
#pragma warning disable IDE0032
[StructLayout(LayoutKind.Explicit)]
[StructLayout(LayoutKind.Explicit, Size = 16)]
public unsafe struct Handle : IDisposable
{
[FieldOffset(8)] private readonly CacheLineMetadata* _head;
[FieldOffset(0)] private nuint _handle;
private Handle(
nuint handle,
CacheLineMetadata* cacheRefCount
[FieldOffset(8)] private nuint _reserved;
internal Handle(
nuint handle,
nuint reserved
)
{
_handle = handle;
_head = cacheRefCount;
_reserved = reserved;
}
[Obsolete("You should not create a CacheLineHandle instance outside a L4", true)]
@@ -88,7 +77,7 @@ namespace StgSharp.HighPerformance.Memory
return;
}
_handle = 0;
_ = Interlocked.Decrement(ref _head->RefCount);
_reserved = 0;
}
}
@@ -0,0 +1,139 @@
# L4 运行模型
> 工作稿,2026-10-14。本文替代旧版 L4 设计草案,区分当前代码、已讨论方向和仍待确认的问题。
## 1. 范围与职责
每个 `L4` 实例绑定一个 NUMA 节点,拥有本地 arena,其中放置 TLSF 分配器状态和 L4 Swiss 映射表的双缓冲区。
- **L4** 负责本地分配、映射、缓冲区发布和阶段协调。
- **Swiss** 保存并查询 L4 使用的映射/预测条目,不解释 source layout。
- **Predictor** 解释自己的 source spec,生成 packed 数据,并负责格式相关的软件写回或撤销。
- **PredictorMonitor** 计划负责预测器注册、稳定 ID、执行顺序和生命周期;它与 Swiss 中的预测任务/映射状态是两回事。
本文不假设自动写回。若 packed 数据需要写回,必须能识别其所属预测器并显式调用对应逻辑。
## 2. AddressSource 与缓存行数据
`AddressSource` 是 16 字节 key:
- `Address`:源地址或 source identity。
- `Description`:64 位 source/layout spec,由所属预测器解释。
当前 Swiss 条目使用该 key 和一个 `ulong` value。源码中的 `CacheLineMetadata` 是 32 字节 TLSF metadata;`L4.Handle` 仍是 16 字节 ABI 占位。引用计数、驱逐保护和写回归属尚未端到端实现。
### 当前与候选 KVP 布局
- 设计讨论中的 `ushort` 是缓存行计数,不是预测器数量或预测器注册表容量。
- 当前 Swiss bucket 将 key 与 `ulong` value 分开组织;现有的 8-KVP 交叉存储描述的是数据存储布局,不是 H1/hash 探测分组。
- 一个候选布局是把 value 扩成 16 字节:保留现有 `ulong` cache-line metadata,再增加 8 字节存放预测计数等状态。与 16 字节 `AddressSource` 组成 32 字节 KVP,每条 64 字节 cache line 放两个 KVP。
- 该方案旨在减少当前 8-KVP 交叉存储造成的 cache-line 分散/冲突。它只调整 KVP 的存储编排,不意味着 H1 探测组从当前 16 槽改成 8 槽。此方案尚未实现,需实测对查找和替换扫描的影响。
## 3. Swiss 映射
当前表有 1024 组,每组 16 个槽位。控制字节扫描使用 SSE2;x64 路径使用 CRC32C 混合两个 64 位 key 字段。两个表缓冲区都分配在 L4 arena 内。
published buffer 地址以 `ulong` 保存:
- `TryGet` 用一次 `Volatile.Read` 捕获 published pointer,并在整个查找中使用该快照。
- `SwapBuffers` 用 `Interlocked.Exchange` 发布下一缓冲区,返回的旧 published pointer 成为下一轮 inactive write buffer。
- 原子发布指针不代表旧 buffer 可以立即复用。Map/swap 阶段协议必须先确保读者不再访问旧 buffer,writer 才能清空或覆盖它。
当前只实现 x64/SSE4.2 后端,其他 ISA 后端尚未实现。
## 4. 预测器接口与阶段
当前接口骨架位于 `L4Predictor.cs`:
- `IL4Predictor` 是非泛型管理接口,暴露预测器 ID 和同步的 `PredictMission(in Span<AddressSource>)`。
- `IL4Predictor<TMission>` 为预测器实现提供强类型 mission API。
- `PredictMission` 填充调用方提供的 span,并返回有效预测数。span 可以由调用方 `stackalloc`,不得逃逸出本次同步调用。
- `PredictorMonitor` 目前是空占位;预测器注册和生命周期尚未实现。
当前讨论中的 L4 生命周期分为不同阶段:
1. **注册预测器**:monitor 按稳定顺序保存异构预测器。固定容量数组(讨论过 256)是候选,其容量与缓存行计数无关。
2. **Prefetch**:预测器按依赖顺序执行。每个 L4 同时只有一个活动 prefetch worker,但 prefetch 可与 Map 重叠,也可能跨 NUMA 节点访问目标数据。
3. **发布**:完成后的 inactive Swiss buffer 被原子发布。Swiss 保存预测/映射任务状态;预测器注册表不是预测任务队列。
4. **Map**:调用方查询已发布的 `AddressSource` 并取得 L4 handle。
5. **撤销/写回**:所属预测器解释 packed 数据,并按需执行软件写回或撤销。
预测器接口本身是同步的。若预测结果需要在 `PredictMission` 返回后或另一线程继续使用,必须先复制到生命周期足够长的自有存储中。异步边界应位于拥有的 batch/work item 周围,而不能让 stack span 逃逸。
## 5. 预测窗口与 Map 同步
两个阈值含义不同:
- `_low_predict = 512`:预测触发/补充阈值。
- `_predict_slow_path_count = 2 * LogicalCoreCount`:按 L4 绑定 NUMA 节点逻辑处理器数量计算的提前慢路径窗口。
当前 Map 的分流意图如下:
| 剩余预测数 | Map 行为 |
|---|---|
| 小于 512 | 尝试成为预测 writer;预测可能完成并发布新 buffer。竞争失败的调用走带读者计数的查找路径。 |
| 512 到 `512 + _predict_slow_path_count` 之间 | 走带计数的慢查找路径,以便 swap 等待这些读者退出。 |
| 大于或等于 `512 + _predict_slow_path_count` | 走无计数的直接查找快路径。 |
计数读者协议先等待 swap gate 打开,再增加 reader count 并复查 gate。如果 gate 在两步之间关闭,则撤销计数并重试。swap 方关闭 gate、等待已计数读者归零、交换 buffer、发布新的剩余预测数,最后重新打开 gate。
无计数快路径依赖预测提前量和阶段时序。这是性能启发式,本身不能证明一个被抢占的 reader 不会在旧 buffer 复用时仍继续访问。无计数 reader 与 buffer 生命周期之间的不变量,以及快慢路径边界,都仍需验证。
当前 `Predict()` 返回 `int`,但实现仍是返回 `0` 的空 stub,因此预测数生命周期尚未端到端运行。
## 6. 预测器注册、任务状态与在途缓冲
预测器调度和预测任务执行是两条相关但不同的生命周期,不能把“预测器已预测完”当成“它产生的工作已完成”。
- **待调度预测器(多个)**:保存在 PredictorMonitor 的有序管理数组/队列中,等待轮到自己。固定槽位容量(曾讨论 256)表示预测器注册上限,与缓存行计数无关。
- **活动预测器(最多一个)**:当前 L4 正在执行其同步 `PredictMission`。预测器之间按依赖顺序切换,不需要多 lane 或 thread-local lane 归属。
- **在途预测批次(可以多个)**:某预测器已经产出候选,但候选对应的实际任务尚未全部执行。此时要保留稳定的 predictor ID/代次、mission 或写回上下文;该记录属于任务 bag,不等同于预测器注册槽位。`PredictMission` 按预测器依赖顺序串行调用;前一批次的实际任务可以仍在途,是否与后续批次重叠由任务依赖决定。
- **已完成批次**:对应实际工作全部完成,且所需软件写回或显式丢弃已完成。此时释放在途记录及其引用资源,不再由 L4 保存该批次。预测器本身是否注销是另一项生命周期操作。
Swiss 负责预测任务/映射条目的查找状态;Monitor 负责预测器注册和调度;在途预测器缓冲负责尚未完成的预测工作及其归属。三者不可混为同一个队列或数组。
`PredictMission(in Span<AddressSource>)` 是同步接口,L4 可传入 stackalloc span。若返回后仍需跨线程或跨 NUMA 处理其内容,必须将内容复制到拥有明确生命周期的缓冲区;不得让 stack span 逃逸。由于预测器顺序有依赖,待处理预测必须按既定顺序交接。若采用异步 worker,队列应有界且不阻塞 Map 快路径。
## 7. 驱逐与写回策略
L4 不得推断或解码预测器的 packed 格式。若丢弃一个尚未写回、且对应实际工作仍在途的预测条目会丢失必要状态,就不能静默驱逐它;必须由缓存行保护/引用状态阻止替换,或由 Monitor/预测器先完成写回、撤销或显式释放。相关状态可放在 KVP metadata 或 sidecar,布局尚未定稿。
owner ID 可用于路由显式写回;但若多个预测器能独立声明同一条目,单一 owner 字段不够。引用计数可保护共享条目,而写回路径仍需获得预测器 identity/source spec。
## 8. 当前状态与待决问题
**源码中已实现:**
- NUMA 绑定的 L4 arena 和 TLSF 包装。
- 双缓冲 Swiss 表及原子 published pointer 交换。
- x64 CRC32C hash 和 SSE2 控制字节扫描。
- 固定预测触发阈值,以及按 NUMA 节点逻辑处理器数量计算的提前慢路径窗口。
- 慢路径的 Map reader gate/counter。
- 同步的泛型/非泛型预测器接口骨架。
**尚未实现或尚未验证:**
- PredictorMonitor 注册表容量、注册/注销和生命周期状态转换。
- 实际预测算法与有效预测条目的生成。
- 端到端撤销/写回归属和缓存行驱逐保护。
- Prefetch 与 Map 跨 NUMA 重叠时的自有 queue/batch 交接方式。
- 无计数 Map 快路径不会与 published buffer 复用重叠的证明/测试。
- 覆盖预测器发布、Map、撤销和 Dispose 的并发压力测试。
## 9. L4 的四个核心操作
### 预测(Prefetch)
Monitor 按注册顺序选择预测器,调用其同步预测接口;预测器解释自己的 source spec,将预测项写入调用方提供的 span。Swiss 管理预测/映射条目。若预测项对应的实际工作尚未完成,则放入在途预测器缓冲,保留后续完成、撤销或写回所需的归属信息。预测期间若没有可用的 cache-line 槽位,替换/保护策略由后续设计决定,不能隐式丢弃仍在途的条目。
### 映射(Map)
计算 worker 按预测器调度获取 `AddressSource`,通过 Swiss 查询 NUMA 本地的 packed 数据并得到 L4 handle。Map 不解释 `Description`,也不负责预测器注册或写回。
### 注册预测器
将预测器放入 `PredictorMonitor` 的管理槽位,分配稳定 ID 和执行顺序,并纳入生命周期监视。注册表槽位与 Swiss 预测条目、在途预测工作是不同资源。
### 移除预测器
先停止向该预测器分派新 mission,再等待其在途预测对应的实际工作完成或被撤销;需要的软件写回完成后,才能释放在途状态并回收注册槽位。具体等待与写回协议尚待设计。
@@ -0,0 +1,111 @@
// -----------------------------------------------------------------------------
// file="L4.Map"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Nitload.Common.HighPerformance.Memory
{
public unsafe partial class L4
{
private const int _low_predict = 512;
private int _predict_count;
private readonly int _predict_slow_path_count;
private int _swiss_reader_counter;
private int _swiss_swap_lock;
private int _swiss_writer_lock;
public partial Handle Map(
AddressSource source
)
{
ulong handle;
int predictCount = Volatile.Read(ref _predict_count);
if (predictCount < _low_predict)
{
if (Interlocked.CompareExchange(ref _swiss_writer_lock, 1, 0) == 0)
{
try
{
int effectivePredictionCount = Predict();
if (effectivePredictionCount > 0)
{
_ = Interlocked.Exchange(ref _swiss_swap_lock, 1);
while (Volatile.Read(ref _swiss_reader_counter) != 0) { }
_addressMap.SwapBuffers();
Volatile.Write(ref _predict_count, effectivePredictionCount);
_addressMap.TryRehash();
}
if (_addressMap.TryGet(source, out handle)) {
throw new InvalidOperationException($"Unexpected map request: [address source: 0x{source.Address:x16}; layout profile: {source.Description:x16}].");
}
}
finally
{
_ = Interlocked.Exchange(ref _swiss_swap_lock, 0);
_ = Interlocked.Exchange(ref _swiss_writer_lock, 0);
}
} else if (!TryGetAddress(source, out handle)) {
throw new InvalidOperationException($"Unexpected map request: [address source: 0x{source.Address:x16}; layout profile: {source.Description:x16}].");
}
} else if (predictCount < _low_predict + _predict_slow_path_count)
{
if (!TryGetAddress(source, out handle)) {
throw new InvalidOperationException($"Unexpected map request: [address source: 0x{source.Address:x16}; layout profile: {source.Description:x16}].");
}
} else if (!_addressMap.TryGet(source, out handle)) {
throw new InvalidOperationException($"Unexpected map request: [address source: 0x{source.Address:x16}; layout profile: {source.Description:x16}].");
}
CacheLineMetadata* metadata = (CacheLineMetadata*)handle;
// process metadata, unfinished
uint offset = metadata->PositionMask;
nuint address = /*base ptr + */ offset;
_ = Interlocked.Decrement(ref _predict_count);
return new Handle(address, (nuint)handle);
}
private bool TryGetAddress(
AddressSource source,
out ulong handle
)
{
while (true)
{
while (Volatile.Read(ref _swiss_swap_lock) != 0) { }
_ = Interlocked.Increment(ref _swiss_reader_counter);
if (Volatile.Read(ref _swiss_swap_lock) == 0)
{
break;
}
_ = Interlocked.Decrement(ref _swiss_reader_counter);
}
try
{
return _addressMap.TryGet(source, out handle);
}
finally
{
_ = Interlocked.Decrement(ref _swiss_reader_counter);
}
}
}
}
@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------------
// file="L4.Predict"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Nitload.Common.HighPerformance.Memory
{
public unsafe partial class L4
{
internal partial int Predict()
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,117 @@
// -----------------------------------------------------------------------------
// file="L4.PredictorQueue"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Nitload.Common.HighPerformance.Memory
{
public partial class L4
{
private readonly IL4Predictor[] _ring = new IL4Predictor[256];
private volatile int _consume;
private volatile int _enter;
private volatile int _predict;
private int _queue_sync;
internal void DropPredictor(
IL4Predictor predictor
)
{
if (predictor is null) {
return;
}
SpinWait sp = new();
while (Interlocked.CompareExchange(ref _queue_sync, 1, 0) != 0) {
sp.SpinOnce();
}
try
{
unchecked
{
for (int i = _consume; i < _predict; i++)
{
byte idx = (byte)i;
if (ReferenceEquals(predictor, _ring[idx]))
{
int end = _predict;
for (int move = i; move < end - 1; move++) {
_ring[(byte)move] = _ring[(byte)(move + 1)];
}
_ring[(byte)(end - 1)] = default!;
_predict = end - 1;
return;
}
}
}
}
finally
{
_ = Interlocked.Exchange(ref _queue_sync, 0);
}
}
internal void RegistPredictor(
IL4Predictor predictor
)
{
if (predictor is null) {
return;
}
SpinWait sp = new();
while (Interlocked.CompareExchange(ref _queue_sync, 1, 0) != 0) {
sp.SpinOnce();
}
WaitForSpare();
_consume++;
try
{
unchecked
{
byte idx = (byte)_consume;
_ring[idx] = predictor;
}
}
finally
{
_ = Interlocked.Exchange(ref _queue_sync, 0);
}
}
private void WaitForSpare()
{
int spin_count = 0;
// wait until the queue has space
while (_enter - _consume >= 256)
{
switch (spin_count)
{
case < 1024:
spin_count++;
break;
case < 65536:
spin_count++;
Thread.SpinWait(1);
break;
default:
Thread.Sleep(1);
break;
}
}
}
}
}
@@ -0,0 +1,289 @@
// -----------------------------------------------------------------------------
// file="L4.SwissTable"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Nitload.Common.HighPerformance.Memory
{
public sealed unsafe partial class L4
{
private const int BucketCapacity = 16;
private const int GroupCount = 1 << GroupCountLog;
private const int GroupCountLog = 10;
private const int IdealCount = GroupCount * BucketCapacity;//16384
private const int OffsetOfBucket = IdealCount;
private static readonly int TableBufferSize =
IdealCount + (GroupCount * Unsafe.SizeOf<SwissBucket>());
internal static readonly int BufferSize = TableBufferSize * 2;
private enum SwissControl : byte
{
Empty = 0b10000000,
Deleted = 0b10000001
}
[StructLayout(LayoutKind.Sequential)]
public struct AddressSource : IEquatable<AddressSource>
{
public ulong Address;
public ulong Description;
public readonly bool Equals(
AddressSource other
)
{
return this == other;
}
public override readonly bool Equals(
object? obj
)
{
return obj is AddressSource source && Equals(source);
}
public override readonly int GetHashCode()
{
return HashCode.Combine(Address, Description);
}
public static bool operator !=(
AddressSource left,
AddressSource right
)
{
return !(left == right);
}
public static bool operator ==(
AddressSource left,
AddressSource right
)
{
return left.Address == right.Address && left.Description == right.Description;
}
}
[InlineArray(BucketCapacity)]
internal struct KeyArray
{
private AddressSource _element0;
}
internal struct SwissBucket
{
public KeyArray Keys;
public fixed ulong Values[BucketCapacity];
}
internal unsafe struct SwissEnumerator(
byte* buffer
)
{
private readonly SwissBucket* _buckets = (SwissBucket*)(buffer + IdealCount);
private readonly Vector128<byte>* _control_bytes = (Vector128<byte>*)buffer;
private int _current_group;
private int _current_mask;
private int _current_offset;
private bool _group_loaded;
public (AddressSource key, ulong value) Current
{
get
{
SwissBucket* bucket = &_buckets[_current_group];
AddressSource key = bucket->Keys[_current_offset];
ulong value = bucket->Values[_current_offset];
return (key, value);
}
}
public bool MoveNext()
{
while (_current_group < GroupCount)
{
if (!_group_loaded)
{
if (Sse42.IsSupported)
{
_current_mask = SwissReaderX86.CompareOccupiedMask(*(_control_bytes + _current_group));
} else
{
throw new PlatformNotSupportedException();
}
_group_loaded = true;
}
if (_current_mask != 0)
{
_current_offset = BitOperations.TrailingZeroCount(_current_mask);
_current_mask &= _current_mask - 1;
return true;
}
_current_group++;
_group_loaded = false;
}
return false;
}
}
internal unsafe struct SwissTable : IDisposable
{
private byte* _buffer_write;
private int _delete_count;
private ulong _published_buffer;
private SwissTable(
byte* buffers
)
{
_published_buffer = (ulong)buffers;
_buffer_write = buffers + TableBufferSize;
InitializeBuffer(buffers);
InitializeBuffer(_buffer_write);
}
internal byte* ReadBufferForLockFreeBenchmark => (byte*)Volatile.Read(ref _published_buffer);
internal readonly byte* WriteBufferForLockFreeBenchmark => _buffer_write;
public void Dispose()
{
_ = Interlocked.Exchange(ref _published_buffer, 0);
_buffer_write = null;
}
/// <summary>
/// Publishes the write buffer. The caller must provide the phase barrier around this
/// operation.
/// </summary>
public void SwapBuffers()
{
ulong previousBuffer = Interlocked.Exchange(ref _published_buffer, (ulong)_buffer_write);
_buffer_write = (byte*)previousBuffer;
}
/// <summary>
/// Writes to the inactive buffer. The caller must serialize all writes.
/// </summary>
public bool TryAddOrSet(
AddressSource key,
ulong value
)
{
if (Sse42.IsSupported) {
return SwissWriterX86.TryAddOrSet(_buffer_write, key, value, ref _delete_count);
}
throw new NotImplementedException("The Swiss backend is not implemented for this ISA.");
}
/// <summary>
/// Reads the published buffer. The caller must prevent swaps until all reads finish.
/// </summary>
public bool TryGet(
AddressSource key,
out ulong value
)
{
byte* publishedBuffer = (byte*)Volatile.Read(ref _published_buffer);
if (Sse42.IsSupported) {
return SwissReaderX86.TryGet(publishedBuffer, key, out value);
}
throw new NotImplementedException("The Swiss backend is not implemented for this ISA.");
}
public void TryRehash()
{
if (_delete_count < 2048)
{
NativeMemory.Copy(_buffer_write, (byte*)_published_buffer, (nuint)TableBufferSize);
return;
}
try
{
SwissEnumerator e = new((byte*)_published_buffer);
InitializeBuffer(_buffer_write);
if (Sse42.IsSupported)
{
while (e.MoveNext())
{
(AddressSource key, ulong value) = e.Current;
if (!SwissWriterX86.TryAddOrSet(_buffer_write, key, value, ref _delete_count))
{
throw new InvalidOperationException("The Swiss table could not fit all active entries during rehash.");
}
}
_delete_count = 0;
} else
{
throw new PlatformNotSupportedException();
}
}
catch (Exception)
{
NativeMemory.Copy(_buffer_write, (byte*)_published_buffer, (nuint)TableBufferSize);
throw;
}
}
/// <summary>
/// Removes from the inactive buffer. The caller must serialize all writes.
/// </summary>
public bool TryRemove(
AddressSource key,
out ulong value
)
{
if (Sse42.IsSupported)
{
return SwissWriterX86.TryRemove(_buffer_write, key, out value, ref _delete_count);
} else
{
throw new NotImplementedException("The Swiss backend is not implemented for this ISA.");
}
}
internal static SwissTable Create(
byte* buffers
)
{
ArgumentNullException.ThrowIfNull(buffers);
return new SwissTable(buffers);
}
private static void InitializeBuffer(
byte* buffer
)
{
Unsafe.InitBlock(buffer, (byte)SwissControl.Empty, IdealCount);
}
}
}
}
@@ -0,0 +1,240 @@
// -----------------------------------------------------------------------------
// file="L4.Swiss_X86"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Nitload.Common.HighPerformance.Memory
{
public partial class L4
{
private const uint CrcMagic = 0x7f1c9961;
internal static class SwissHashX64
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Compute(
AddressSource key
)
{
ulong hash = CrcMagic;
hash = Sse42.X64.Crc32(hash, key.Address);
hash = Sse42.X64.Crc32(hash, key.Description);
return (uint)hash;
}
}
internal readonly unsafe struct SwissWriterX86
{
public static bool TryAddOrSet(
byte* buffer,
AddressSource key,
ulong value,
ref int delete_count
)
{
Vector128<byte>* control_bytes = (Vector128<byte>*)buffer;
SwissBucket* buckets = (SwissBucket*)(buffer + IdealCount);
uint hash = SwissHashX64.Compute(key);
uint begin = hash % GroupCount;
uint first_tomb_group = 0;
int first_tomb_idx = -1;
byte target_hash = (byte)((hash >> GroupCountLog) & ~(1 << 7));
for (int i = 0; i < GroupCount; i++)
{
Vector128<byte>* control_bytes_ptr = control_bytes + begin;
Vector128<byte> control_bytes_v128 = Sse2.LoadAlignedVector128((byte*)control_bytes_ptr);
Vector128<byte> target_hash_v128 = Vector128.Create(target_hash);
Vector128<byte> target_empty_v128 = Vector128.Create((byte)SwissControl.Empty);
Vector128<byte> target_tomb_v128 = Vector128.Create((byte)SwissControl.Deleted);
Vector128<byte> cmp_hash = Sse2.CompareEqual(control_bytes_v128, target_hash_v128);
Vector128<byte> cmp_empty = Sse2.CompareEqual(control_bytes_v128, target_empty_v128);
Vector128<byte> cmp_tomb = Sse2.CompareEqual(control_bytes_v128, target_tomb_v128);
int hash_mask = Sse2.MoveMask(cmp_hash);
int empty_mask = Sse2.MoveMask(cmp_empty);
int tomb_mask = Sse2.MoveMask(cmp_tomb);
if (tomb_mask != 0 && first_tomb_idx < 0)
{
int index = BitOperations.TrailingZeroCount(tomb_mask);
first_tomb_group = begin;
first_tomb_idx = index;
}
while (hash_mask != 0)
{
int index = BitOperations.TrailingZeroCount(hash_mask);
SwissBucket* bucket = &buckets[begin];
if (bucket->Keys[index] == key)
{
bucket->Values[index] = value;
return true;
}
hash_mask &= ~(1 << index);
}
while (empty_mask != 0)
{
int index = BitOperations.TrailingZeroCount(empty_mask);
if (first_tomb_idx < 0)
{
SwissBucket* bucket = &buckets[begin];
bucket->Keys[index] = key;
bucket->Values[index] = value;
((byte*)control_bytes_ptr)[index] = target_hash;
} else
{
SwissBucket* bucket = &buckets[first_tomb_group];
bucket->Keys[first_tomb_idx] = key;
bucket->Values[first_tomb_idx] = value;
((byte*)(control_bytes + first_tomb_group))[first_tomb_idx] = target_hash;
delete_count--;
}
return true;
}
begin = (begin + 1) & (GroupCount - 1);
}
if (first_tomb_idx >= 0)
{
SwissBucket* bucket = &buckets[first_tomb_group];
bucket->Keys[first_tomb_idx] = key;
bucket->Values[first_tomb_idx] = value;
((byte*)(control_bytes + first_tomb_group))[first_tomb_idx] = target_hash;
delete_count--;
return true;
}
value = 0;
return false;
}
public static bool TryRemove(
byte* buffer,
AddressSource key,
out ulong value,
ref int delete_count
)
{
Vector128<byte>* control_bytes = (Vector128<byte>*)buffer;
SwissBucket* buckets = (SwissBucket*)(buffer + IdealCount);
uint hash = SwissHashX64.Compute(key);
uint begin = hash % GroupCount;
byte target_hash = (byte)((hash >> GroupCountLog) & ~(1 << 7));
for (int i = 0; i < GroupCount; i++)
{
Vector128<byte> control_bytes_v128 = Sse2.LoadAlignedVector128((byte*)(control_bytes + begin));
Vector128<byte> target_hash_v128 = Vector128.Create(target_hash);
Vector128<byte> cmp_hash = Sse2.CompareEqual(control_bytes_v128, target_hash_v128);
int mask = Sse2.MoveMask(cmp_hash);
while (mask != 0)
{
int index = BitOperations.TrailingZeroCount(mask);
SwissBucket* bucket = &buckets[begin];
if (bucket->Keys[index] == key)
{
((byte*)(control_bytes + begin))[index] = (byte)SwissControl.Deleted;
value = bucket->Values[index];
delete_count++;
return true;
}
mask &= ~(1 << index);
}
Vector128<byte> target_empty_v128 = Vector128.Create((byte)SwissControl.Empty);
Vector128<byte> cmp_empty = Sse2.CompareEqual(control_bytes_v128, target_empty_v128);
int empty_mask = Sse2.MoveMask(cmp_empty);
if (empty_mask != 0)
{
break;
}
begin = (begin + 1) & (GroupCount - 1);
}
value = 0;
return false;
}
}
internal readonly unsafe struct SwissReaderX86
{
private static readonly Vector128<byte> _empty_mask = Vector128.Create((byte)SwissControl.Empty);
private static readonly Vector128<byte> _tomb_mask = Vector128.Create((byte)SwissControl.Deleted);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CompareOccupiedMask(
Vector128<byte> control_bytes_v128
)
{
Vector128<byte> cmp_eql = Sse2.CompareEqual(control_bytes_v128, _empty_mask);
Vector128<byte> cmp_del = Sse2.CompareEqual(control_bytes_v128, _tomb_mask);
int specialMask = Sse2.MoveMask(Sse2.Or(cmp_eql, cmp_del));
return (ushort)~(ushort)specialMask;
}
public static bool TryGet(
byte* buffer,
AddressSource key,
out ulong value
)
{
Vector128<byte>* control_bytes = (Vector128<byte>*)buffer;
SwissBucket* buckets = (SwissBucket*)(buffer + IdealCount);
uint hash = SwissHashX64.Compute(key);
uint begin = hash % GroupCount;
byte target_hash = (byte)((hash >> GroupCountLog) & ~(1 << 7));
for (int i = 0; i < GroupCount; i++)
{
Vector128<byte> control_bytes_v128 = Sse2.LoadAlignedVector128((byte*)(control_bytes + begin));
Vector128<byte> target_hash_v128 = Vector128.Create(target_hash);
Vector128<byte> cmp_hash = Sse2.CompareEqual(control_bytes_v128, target_hash_v128);
int mask = Sse2.MoveMask(cmp_hash);
while (mask != 0)
{
int index = BitOperations.TrailingZeroCount(mask);
SwissBucket* bucket = &buckets[begin];
if (bucket->Keys[index] == key)
{
value = bucket->Values[index];
return true;
}
mask &= ~(1 << index);
}
Vector128<byte> target_empty_v128 = Vector128.Create((byte)SwissControl.Empty);
Vector128<byte> cmp_empty = Sse2.CompareEqual(control_bytes_v128, target_empty_v128);
if (Sse2.MoveMask(cmp_empty) != 0)
{
break;
}
begin = (begin + 1) & (GroupCount - 1);
}
value = 0;
return false;
}
}
}
}
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance.ProcessorAbstraction;
using Nitload.Common.HighPerformance.ProcessorAbstraction;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,25 +14,26 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
public partial class L4
{
private unsafe class TLSF
private unsafe struct TLSF
{
public const int MaxCacheLineCount = 8192;
public static readonly nuint RequestedAllocation = (nuint)MaxCacheLineCount * CacheLineMetadata.SizeOf;
private readonly short[] _bucket;
public static readonly nuint RequestedAllocation =
((nuint)MaxCacheLineCount + 1u) * CacheLineMetadata.SizeOf;
private readonly CacheLineMetadata* _spareMemory;
private readonly int _align;
private readonly int _maxLevel;
private int _metadataEnd;
private readonly Ptr64 _basePtr;
private fixed short _bucket[64];
private readonly uint _maxAllocSize;
private readonly uint _totalSize;
@@ -48,7 +49,9 @@ namespace StgSharp.HighPerformance.Memory
_maxAllocSize = uint.Max(64, (~64U) & (uint.Min(maxBlockSize, CacheLineMetadata.MaxBufferSize) + 63U));
_maxLevel = int.MaxValue;
_maxLevel = GetLevel(_maxAllocSize);
_bucket = new short[GetLevel(_maxAllocSize) + 2];
// _bucket = new short[GetLevel(_maxAllocSize) + 2];
_bucket[63] = (short)(GetLevel(_maxAllocSize) + 2);
_spareMemory = (CacheLineMetadata*)(ulong)(_basePtr + _totalSize);
_metadataEnd = 0;
@@ -268,7 +271,7 @@ namespace StgSharp.HighPerformance.Memory
idx = candidate->NextLevel;
}
}
for (int i = level + 1; i < _bucket.Length; i++)
for (int i = level + 1; i < _bucket[63]; i++)
{
int idx = _bucket[i];
if (idx != 0)
@@ -0,0 +1,99 @@
// -----------------------------------------------------------------------------
// file="L4"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using Nitload.Common.HighPerformance.ProcessorAbstraction;
namespace Nitload.Common.HighPerformance.Memory
{
public sealed unsafe partial class L4 : IDisposable
{
private const nuint PageSize = 4096;
private bool _disposed;
private readonly int _bindedNuma;
private readonly nuint _baseAddress;
private SwissTable _addressMap;
private TLSF _bufferAllocator;
public L4(
int bindedNuma,
uint bufferSize = 8192 * 8192,
int align = 16,
uint maxCacheLineSize = 8192
)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(bufferSize, CacheLineMetadata.MaxBufferSize);
ArgumentOutOfRangeException.ThrowIfGreaterThan(maxCacheLineSize, CacheLineMetadata.MaxBufferSize);
_bindedNuma = bindedNuma;
_predict_slow_path_count = GetPredictSlowPathCount(bindedNuma);
nuint normalizedBufferSize = (bufferSize + (PageSize - 1)) & (~(PageSize - 1));
ByteCapacity = normalizedBufferSize;
nuint swissBufferOffset = normalizedBufferSize + TLSF.RequestedAllocation;
AllocSize = swissBufferOffset + (nuint)BufferSize;
_baseAddress = (nuint)Numa.Alloc(bindedNuma, AllocSize);
_bufferAllocator = new TLSF(bufferSize, maxCacheLineSize, (void*)_baseAddress);
_addressMap = SwissTable.Create((byte*)(_baseAddress + swissBufferOffset));
}
public nuint ByteCapacity { get; init; }
public nuint AllocSize { get; init; }
public void Dispose()
{
if (_disposed) {
return;
}
_disposed = true;
_addressMap.Dispose();
Numa.Free((void*)_baseAddress, _bindedNuma, AllocSize);
GC.SuppressFinalize(this);
}
public partial Handle Map(
AddressSource source
);
internal void Collect()
{
ObjectDisposedException.ThrowIf(_disposed, this);
_bufferAllocator.Collect();
}
internal void Free(
int handle
)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_bufferAllocator.Free(handle);
}
internal bool TryAllocate(
uint size,
out int handle
)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _bufferAllocator.TryAlloc(size, out handle);
}
internal partial int Predict();
private static int GetPredictSlowPathCount(int bindedNuma)
{
foreach (NumaNode node in Numa.AllNumaNode)
{
if (node.NumaNodeId == (uint)bindedNuma)
return checked(node.LogicalCoreCount * 2);
}
throw new ArgumentOutOfRangeException(nameof(bindedNuma), "No topology information exists for the bound NUMA node.");
}
}
}
@@ -0,0 +1,36 @@
// -----------------------------------------------------------------------------
// file="L4Predictor"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Nitload.Common.HighPerformance.Memory
{
public interface IL4Predictor
{
int Id { get; set; }
int PredictMission(
in Span<L4.AddressSource> RequiredBuffer
);
}
public interface IL4Predictor<TMission> : IL4Predictor
{
bool TryGetNextMission(
out TMission mission
);
}
}
@@ -14,7 +14,7 @@ using System.Numerics;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
#pragma warning disable CA2225
@@ -8,7 +8,7 @@
using System;
using System.Runtime.CompilerServices;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
public unsafe struct SlabAllocationHandle<T> : IDisposable where T : unmanaged
{
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Threading;
using Nitload.Threading;
using System;
using System.Collections.Generic;
@@ -13,7 +13,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
internal sealed unsafe class ChunkedSlabAllocator<T> : SlabAllocator<T> where T : unmanaged
{
@@ -40,15 +40,28 @@ namespace StgSharp.HighPerformance.Memory
> 64 => 64,
_ => (int)count
};
Span<nuint> span = stackalloc nuint[initCount];
for (int i = 0; i < initCount; i++) {
span[i] = _currentBuffer + ((nuint)(i * _elementSize));
}
_buffers = new(4);
_buffers.Push(_currentBuffer);
_recycle = BufferStackBuilder.Create(span);
// Fill through a callback rather than building a scratch span and
// passing it to BufferStackBuilder.Create: no stackalloc, no copy,
// and no reliance on Span-to-ReadOnlySpan inference (CS0411 before
// C# 14). Same shape as FixedSlabAllocator.
int elementSize = _elementSize;
_recycle = new(initCount);
_recycle.FillRange(init, _currentBuffer);
_currentCapacity = count;
_currentIndex = (nuint)initCount;
void init(
Span<nuint> arr,
nuint buffer
)
{
for (int i = 0; i < arr.Length; i++) {
arr[i] = buffer + ((nuint)(i * elementSize));
}
}
}
public override nuint Allocate()
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Threading;
using Nitload.Threading;
using System;
using System.Collections.Generic;
@@ -16,7 +16,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
internal sealed unsafe class ConcurrentChunkedSlabAllocator<T> : SlabAllocator<T>
where T : unmanaged
@@ -44,14 +44,26 @@ namespace StgSharp.HighPerformance.Memory
> 64 => 64,
_ => (int)count
};
Span<nuint> span = stackalloc nuint[initCount];
for (int i = 0; i < initCount; i++) {
span[i] = _currentBuffer + (nuint)(i * _elementSize);
}
_buffers = new(4);
_recycle = BufferStackBuilder.CreateConcurrent(span);
// Callback fill instead of scratch span + CreateConcurrent: avoids
// the stackalloc and the copy, and sidesteps the CS0411 inference
// gap. Safe without locking — not yet published to other threads.
int elementSize = _elementSize;
_recycle = new(initCount);
_recycle.FillRange(init, _currentBuffer);
_currentCapacity = count;
_currentIndex = (nuint)initCount;
void init(
Span<nuint> arr,
nuint buffer
)
{
for (int i = 0; i < arr.Length; i++) {
arr[i] = buffer + (nuint)(i * elementSize);
}
}
}
public override nuint Allocate()
@@ -5,9 +5,9 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Collections;
using StgSharp.HighPerformance.Memory;
using StgSharp.Threading;
using Nitload.Common.Collections;
using Nitload.Common.HighPerformance.Memory;
using Nitload.Threading;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,7 +15,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
internal unsafe class FixedSlabAllocator<T> : SlabAllocator<T> where T : unmanaged
{
@@ -40,19 +40,19 @@ namespace StgSharp.HighPerformance.Memory
_buffer = buffer;
_Capacity = count;
int initCount = count switch
_recycle = new((int)_Capacity);
_recycle.FillRange(init, buffer);
_currentIndex = _Capacity;
static void init(
Span<nuint> arr,
nuint buffer
)
{
<= 4 => 4,
> 64 => 64,
_ => (int)count
};
Span<nuint> span = stackalloc nuint[initCount];
for (int i = 0; i < initCount; i++) {
span[i] = _buffer + ((nuint)(i * _elementSize));
for (int i = 0; i < arr.Length; i++) {
arr[i] = buffer + ((nuint)i * (nuint)Unsafe.SizeOf<T>());
}
}
_recycle = CapacityFixedStackBuilder.Create(span);
_Capacity = count;
_currentIndex = (nuint)initCount;
}
public override nuint Allocate()
@@ -5,14 +5,14 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Threading;
using Nitload.Threading;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
public sealed unsafe class ConcurrentSequentialSlabAllocator<T> : SlabAllocator<T>
where T : unmanaged
@@ -43,12 +43,26 @@ namespace StgSharp.HighPerformance.Memory
> 64 => 64,
_ => ((int)_capacity) / 4
};
Span<nuint> span = stackalloc nuint[initCount];
for (int i = 0; i < initCount; i++) {
span[i] = (nuint)i;
}
_stack = BufferStackBuilder.CreateConcurrent(span);
// Callback fill instead of scratch span + CreateConcurrent: avoids
// the stackalloc and the copy, and sidesteps the CS0411 inference
// gap. Safe without locking — not yet published to other threads.
//
// Note this variant stores *indices*, not addresses, so the buffer
// argument is unused here; it is kept to match the shared
// SpanAction<T, nuint> shape.
_stack = new(initCount);
_stack.FillRange(init, 0);
_highestAllocated += (nuint)initCount;
static void init(
Span<nuint> arr,
nuint _
)
{
for (int i = 0; i < arr.Length; i++) {
arr[i] = (nuint)i;
}
}
}
internal T* BasePointer => _buffer;
@@ -5,10 +5,10 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using Nitload.Common.HighPerformance.Memory;
using System;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
public static class SlabAllocator
{
@@ -5,8 +5,8 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using StgSharp.HighPerformance.ProcessorAbstraction;
using Nitload.Common.HighPerformance.Memory;
using Nitload.Common.HighPerformance.ProcessorAbstraction;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -18,9 +18,9 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using static StgSharp.HighPerformance.Memory.TwoLayerSegregatedFitAllocator;
using static Nitload.Common.HighPerformance.Memory.TwoLayerSegregatedFitAllocator;
namespace StgSharp.HighPerformance.Memory
namespace Nitload.Common.HighPerformance.Memory
{
public unsafe class TwoLayerSegregatedFitAllocator : IDisposable
{
@@ -12,7 +12,7 @@ using System.Numerics;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
/// <summary>
/// Abstraction for a wide SIMD register or equivalent packed numeric storage. Provides raw
@@ -11,27 +11,26 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
public readonly struct LogicalCore
{
public readonly uint GlobalCpuId;
public readonly uint NumaGroupId;
/// <summary>
/// Stays 0 on linux.
/// </summary>
public readonly uint ProcessorGroupId;
public readonly uint SocketId;
public readonly uint CpuIndex;
public readonly uint NumaNodeId;
internal LogicalCore(
uint globalCpuId,
uint numaGroupId,
uint processorGroupId,
uint socketId,
uint cpuIndex,
uint numaNodeId
)
{
GlobalCpuId = globalCpuId;
NumaGroupId = numaGroupId;
ProcessorGroupId = processorGroupId;
SocketId = socketId;
CpuIndex = cpuIndex;
NumaNodeId = numaNodeId;
}
@@ -16,7 +16,7 @@ using System.Runtime.Intrinsics.X86;
using System.Text;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
[StructLayout(LayoutKind.Explicit, Pack = 16)]
public unsafe struct M128 : IRegisterPresentation
@@ -15,7 +15,7 @@ using System.Runtime.Intrinsics;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
[StructLayout(LayoutKind.Explicit, Pack = 16)]
public unsafe struct M256 : IRegisterPresentation
@@ -15,7 +15,7 @@ using System.Runtime.Intrinsics;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
[StructLayout(LayoutKind.Explicit, Size = 64, Pack = 64)]
public unsafe struct M512 : IRegisterPresentation
@@ -10,7 +10,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
[StructLayout(LayoutKind.Explicit, Pack = 8)]
public unsafe struct M64 : IRegisterPresentation
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using Nitload.Common.HighPerformance.Memory;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
@@ -14,7 +14,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
public static partial class Numa
{
@@ -33,7 +33,7 @@ namespace StgSharp.HighPerformance.ProcessorAbstraction
if (_numaNodes.Length == 0) {
throw new InvalidOperationException("Numa info has not been loaded.");
}
if ((numaId < 0) || (numaId >= _numaNodes.Length)) {
if ((numaId < 0) || !ContainsNode((uint)numaId)) {
throw new ArgumentOutOfRangeException(nameof(numaId), "Invalid NUMA node ID.");
}
return NumaAllocNative(size, numaId);
@@ -69,22 +69,24 @@ namespace StgSharp.HighPerformance.ProcessorAbstraction
if (_numaNodes.Length == 0) {
throw new InvalidOperationException("Numa info has not been loaded.");
}
if ((numaId < 0) || (numaId >= _numaNodes.Length)) {
if ((numaId < 0) || !ContainsNode((uint)numaId)) {
throw new ArgumentOutOfRangeException(nameof(numaId), "Invalid NUMA node ID.");
}
NumaFreeNative(address, size, numaId);
}
[LibraryImport(Native.LibName, EntryPoint = "sn_get_numa_each")]
[LibraryImport(Native.LibName, EntryPoint = "nfn_get_numa_each")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
public static partial int GetNumaEach(
uint numaId,
Span<ulong> CpuIndexInNuma,
Span<uint> globalCpuIdInNuma
Span<uint> processorGroupIds,
Span<uint> socketIds,
Span<uint> cpuIndices,
Span<uint> numaNodeIds
);
[LibraryImport(Native.LibName, EntryPoint = "sn_get_numa_overall")]
[LibraryImport(Native.LibName, EntryPoint = "nfn_get_numa_overall")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
public static partial int GetNumaOverall(
@@ -97,10 +99,10 @@ namespace StgSharp.HighPerformance.ProcessorAbstraction
if (_numaNodes.Length == 0) {
throw new InvalidOperationException("Numa info has not been loaded.");
}
L4[] buffers = new L4[NumaNodeCount];
for (int i = 0; i < NumaNodeCount; i++)
L4[] buffers = new L4[_numaNodes.Length];
for (int i = 0; i < _numaNodes.Length; i++)
{
L4 buffer = new L4(i);
L4 buffer = new L4((int)_numaNodes[i].NumaNodeId);
buffers[i] = buffer;
}
return buffers;
@@ -108,22 +110,38 @@ namespace StgSharp.HighPerformance.ProcessorAbstraction
internal static void Initialize()
{
Span<int> coreCountInNuma = stackalloc int[(Environment.ProcessorCount / 32) + 1];
int numaNodeCount = 0;
if (0 == GetNumaOverall(ref numaNodeCount, coreCountInNuma)) {
if (0 == GetNumaOverall(ref numaNodeCount, default)) {
throw new InvalidOperationException("Failed to get NUMA overall information.");
}
_numaNodes = new NumaNode[numaNodeCount];
for (uint i = 0; i < numaNodeCount; i++) {
_numaNodes[i] = InitNumaNode(i, coreCountInNuma[(int)i]);
int[] coreCountInNuma = new int[numaNodeCount];
if (0 == GetNumaOverall(ref numaNodeCount, coreCountInNuma)) {
throw new InvalidOperationException("Failed to get NUMA node processor counts.");
}
int activeNodeCount = 0;
for (int i = 0; i < coreCountInNuma.Length; i++) {
if (coreCountInNuma[i] > 0) {
activeNodeCount++;
}
}
NumaNodeCount = activeNodeCount;
_numaNodes = new NumaNode[activeNodeCount];
int nodeIndex = 0;
for (uint numaId = 0; numaId < numaNodeCount; numaId++) {
int coreCount = coreCountInNuma[(int)numaId];
if (coreCount == 0) {
continue;
}
_numaNodes[nodeIndex++] = InitNumaNode(numaId, coreCount);
}
}
[LibraryImport(Native.LibName, EntryPoint = "sn_set_thread_affinity")]
[LibraryImport(Native.LibName, EntryPoint = "nfn_set_thread_affinity")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
internal static partial void SetThreadAffinity(
int coreId
int cpuIndex,
int processorGroupId
);
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -132,12 +150,27 @@ namespace StgSharp.HighPerformance.ProcessorAbstraction
int count
)
{
Span<uint> globalIndex = stackalloc uint[count];
_ = GetNumaEach(numaId, null, globalIndex);
return new(numaId, count, globalIndex);
Span<uint> processorGroupIds = stackalloc uint[count];
Span<uint> socketIds = stackalloc uint[count];
Span<uint> cpuIndices = stackalloc uint[count];
Span<uint> numaNodeIds = stackalloc uint[count];
if (0 == GetNumaEach(numaId, processorGroupIds, socketIds, cpuIndices, numaNodeIds)) {
throw new InvalidOperationException($"Failed to enumerate processors for NUMA node {numaId}.");
}
return new(numaId, count, processorGroupIds, socketIds, cpuIndices, numaNodeIds);
}
[LibraryImport(Native.LibName, EntryPoint = "sn_numa_alloc")]
private static bool ContainsNode(uint numaId)
{
foreach (NumaNode node in _numaNodes) {
if (node.NumaNodeId == numaId) {
return true;
}
}
return false;
}
[LibraryImport(Native.LibName, EntryPoint = "nfn_numa_alloc")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
private static unsafe partial void* NumaAllocNative(
@@ -145,7 +178,7 @@ namespace StgSharp.HighPerformance.ProcessorAbstraction
int numaId
);
[LibraryImport(Native.LibName, EntryPoint = "sn_numa_free")]
[LibraryImport(Native.LibName, EntryPoint = "nfn_numa_free")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
private static unsafe partial void NumaFreeNative(
@@ -12,7 +12,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.HighPerformance.ProcessorAbstraction
namespace Nitload.Common.HighPerformance.ProcessorAbstraction
{
public class NumaNode
{
@@ -22,16 +22,26 @@ namespace StgSharp.HighPerformance.ProcessorAbstraction
internal NumaNode(
uint numaId,
int count,
Span<uint> logicalCoreIds
Span<uint> processorGroupIds,
Span<uint> socketIds,
Span<uint> cpuIndices,
Span<uint> numaNodeIds
)
{
NumaNodeId = numaId;
_logicalCores = new LogicalCore[count];
for (int i = 0; i < count; i++) {
_logicalCores[i] = new LogicalCore(1, numaId, logicalCoreIds[i]);
_logicalCores[i] = new LogicalCore(
processorGroupIds[i],
socketIds[i],
cpuIndices[i],
numaNodeIds[i]);
}
}
public uint NumaNodeId { get; }
public int LogicalCoreCount => _logicalCores.Length;
public ReadOnlySpan<LogicalCore> LogicalCores => _logicalCores;
@@ -10,7 +10,7 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace StgSharp.Internal
namespace Nitload.Common.Internal
{
internal static unsafe partial class DefaultLog
{
@@ -11,13 +11,13 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Internal
namespace Nitload.Common.Internal
{
internal static unsafe partial class Native
{
internal const string LibName =
"StgSharp.Native";
"Nitload.Native";
internal const string LogPath =
"SS_error.log";
@@ -11,7 +11,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp
namespace Nitload
{
public enum LogType
{
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.PipeLine;
using Nitload.Common.PipeLine;
using System;
using System.Collections.Generic;
@@ -13,7 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
public class CrossFrameOperation : IConvertableToPipelineNode
{
@@ -10,7 +10,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
public delegate void PipelineNodeOperation(
in Dictionary<string, PipelineNodeInPort> input,
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Threading;
using Nitload.Threading;
using System;
using System.Collections.Concurrent;
@@ -18,7 +18,7 @@ using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
public static class PipeLineRunner
{
@@ -13,7 +13,7 @@ using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
public static class PipelineMarshal
{
@@ -16,7 +16,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
/// <summary>
///
@@ -13,7 +13,7 @@ using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
public abstract record class PipelineNodeLabel
{
@@ -13,7 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
public class PipelineNodeInPort
{
@@ -14,7 +14,7 @@ using System.Threading;
using System.Xml;
using System.Xml.Linq;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
/// <summary>
/// A sets of complex logic designed for running in multi threads environment. Warning: do not
@@ -15,7 +15,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
public class PipelineConnector
{
@@ -14,7 +14,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.PipeLine
namespace Nitload.Common.PipeLine
{
internal sealed class BeginningNode : PipelineNode
{
@@ -12,7 +12,7 @@ using System.Numerics;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.State
namespace Nitload.State
{
public interface ISimpleStateMachine<TStateNode, TStateLabel, TStateContext>
where TStateNode : SimpleStateMachineNode<TStateNode, TStateLabel, TStateContext>
@@ -13,7 +13,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.State
namespace Nitload.State
{
public abstract class SimpleStateMachineNode<TSelf, TLabel, TStateContext>
where TSelf : SimpleStateMachineNode<TSelf, TLabel, TStateContext>
@@ -11,7 +11,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.State
namespace Nitload.State
{
public abstract class StateContext { }
}
@@ -13,7 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.Threading
namespace Nitload.Threading
{
/// <summary>
/// Provide a lock for managing concurrent access to a buffer that can be expanded and use a
@@ -13,7 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.Threading
namespace Nitload.Threading
{
public class CancellableTask : IDisposable
{
@@ -8,7 +8,7 @@
using System;
using System.Threading;
namespace StgSharp.Threading
namespace Nitload.Threading
{
/// <summary>
/// A single-consumer bell/signal primitive.
@@ -5,13 +5,13 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance.ProcessorAbstraction;
using Nitload.Common.HighPerformance.ProcessorAbstraction;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace StgSharp.Threading
namespace Nitload.Threading
{
public static partial class ThreadHelper
{
@@ -37,7 +37,7 @@ namespace StgSharp.Threading
LogicalCore core
)
{
SetThreadAffinity(core.GlobalCpuId, core.NumaNodeId);
SetThreadAffinity(core.CpuIndex, core.ProcessorGroupId);
}
[Conditional("DEBUG")]
@@ -51,12 +51,12 @@ namespace StgSharp.Threading
#pragma warning disable CA5393
[LibraryImport(Native.LibName, EntryPoint = "sn_set_thread_affinity")]
[LibraryImport(Native.LibName, EntryPoint = "nfn_set_thread_affinity")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
private static partial void SetThreadAffinity(
uint globalCoreId,
uint numaNodeId
uint cpuIndex,
uint processorGroupId
);
#pragma warning restore CA5393
@@ -9,7 +9,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace StgSharp.Threading
namespace Nitload.Threading
{
/// <summary>
/// A counting semaphore with adjustable maximum capacity at runtime. Implemented using one or
@@ -5,13 +5,14 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Collections;
using StgSharp.Timing;
using Nitload.Common.Collections;
using Nitload.Common.HighPerformance.ProcessorAbstraction;
using Nitload.Common.Timing;
using System;
using System.Diagnostics;
using System.Threading;
namespace StgSharp
namespace Nitload
{
public static partial class World
{
@@ -39,7 +40,9 @@ namespace StgSharp
return mainProvider.ElapsedTicks;
}
public sealed override void StartProvidingTime()
public sealed override void StartProvidingTime(
TimeSourceStartupConfiguration threadConfig
)
{
if (mainProvider.IsRunning) {
return;
@@ -49,9 +52,10 @@ namespace StgSharp
timeProvideThread = new Thread(ProvideTime)
{
IsBackground = true,
Name = "StgSharpTime"
Name = "StgSharpTime",
Priority = threadConfig.Priority,
};
timeProvideThread.Start();
timeProvideThread.Start(threadConfig);
}
public override void StopProvidingTime()
@@ -82,13 +86,18 @@ namespace StgSharp
_subscribers.Remove(subscriber);
}
private void ProvideTime()
private void ProvideTime(
object? param
)
{
TimeSourceStartupConfiguration threadConfig = (TimeSourceStartupConfiguration)param!;
long internalFrequency = Stopwatch.Frequency; // ticks per second
lock (mainProvider) {
mainProvider.Restart();
}
Thread.Sleep(10);
Numa.SetThreadAffinity((int)threadConfig.CpuIndex, (int)threadConfig.ProcessorGroupId);
while (mainProvider.IsRunning)
{
long elapsedTicks;
@@ -5,11 +5,11 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.Timing;
using Nitload.Common.Timing;
using System;
using System.Runtime.CompilerServices;
namespace StgSharp.Timing
namespace Nitload.Common.Timing
{
public abstract class TimeSequence
{
@@ -5,14 +5,29 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp;
using Nitload;
using Nitload.Common.HighPerformance.ProcessorAbstraction;
using System;
using System.Linq;
using System.Threading;
namespace StgSharp.Timing
namespace Nitload.Common.Timing
{
public class TimeSourceStartupConfiguration(
nuint processorGroupId = 0,
nuint cpuIndex = 0,
ThreadPriority threadPriority = ThreadPriority.Highest
)
{
public nuint ProcessorGroupId { get; } = processorGroupId;
public nuint CpuIndex { get; } = cpuIndex;
public ThreadPriority Priority { get; } = threadPriority;
}
public abstract class TimeSourceProviderBase
{
@@ -40,7 +55,9 @@ namespace StgSharp.Timing
}
}
public abstract void StartProvidingTime();
public abstract void StartProvidingTime(
TimeSourceStartupConfiguration threadConfig
);
public abstract void StopProvidingTime();
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using Nitload.Common.HighPerformance.Memory;
using System;
using System.Collections.Concurrent;
@@ -13,7 +13,7 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.Timing
namespace Nitload.Common.Timing
{
public partial class TimeSpanProvider
{
@@ -25,8 +25,6 @@ namespace StgSharp.Timing
private EventHandler? _refreshHandler, _missHandler;
private int _size, _id;
private long _pendingSpans; // Number of unconsumed span ticks since last Wait.
private readonly ManualResetEventSlim _event = new();
private readonly object _participantObject = new();
public event EventHandler MissTimeSpanRefreshed
@@ -57,15 +55,17 @@ namespace StgSharp.Timing
/// </summary>
public long PendingSpanCount => Volatile.Read(ref _pendingSpans);
internal ManualResetEventSlim AwaitingEvent => _event;
internal ManualResetEventSlim ResetEvent { get; } = new();
public void Dispose()
{
#pragma warning disable CA1816
// mark provider as ended so external users can observe terminal state
Volatile.Write(ref _endedFlag, 1);
s_unusedID.Push(TokenID);
_event.Set();
ResetEvent.Set();
GC.SuppressFinalize(this);
#pragma warning restore CA1816
}
public override int GetHashCode()
@@ -84,7 +84,7 @@ namespace StgSharp.Timing
public void WaitNextSpan()
{
_event.Wait();
ResetEvent.Wait();
// Consume one span slot (clamp at 0 so multiple subscribers won't make it negative).
while (true)
@@ -99,7 +99,7 @@ namespace StgSharp.Timing
if (cur - 1 == 0)
{
// Last consumer resets the gate for the next tick.
_event.Reset();
ResetEvent.Reset();
}
return;
}
@@ -108,7 +108,7 @@ namespace StgSharp.Timing
public async Task WaitNextSpanAsync()
{
await Task.Run(() => _event.Wait()).ConfigureAwait(false);
await Task.Run(ResetEvent.Wait).ConfigureAwait(false);
while (true)
{
long cur = Volatile.Read(ref _pendingSpans);
@@ -120,7 +120,7 @@ namespace StgSharp.Timing
if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur)
{
if (cur - 1 == 0) {
_event.Reset();
ResetEvent.Reset();
}
break;
@@ -138,7 +138,7 @@ namespace StgSharp.Timing
)
{
// Block the current thread until the event is signaled or cancellation requested.
_event.Wait(ct);
ResetEvent.Wait(ct);
// consume one pending span (same logic as non-cancellable variant)
while (true)
@@ -152,7 +152,7 @@ namespace StgSharp.Timing
if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur)
{
if (cur - 1 == 0) {
_event.Reset();
ResetEvent.Reset();
}
break;
@@ -203,7 +203,7 @@ namespace StgSharp.Timing
if (Interlocked.CompareExchange(ref _pendingSpans, toIssue, 0) == 0)
{
// Open the gate for all current waiters (up to `_size`).
_event.Set();
ResetEvent.Set();
_refreshHandler?.Invoke(this, EventArgs.Empty);
}
} else

Some files were not shown because too many files have changed in this diff Show More