perf: net10.0 zero-alloc hot paths in NamespaceTree + BenchmarkDotNet project#13
Open
Rolling2405 wants to merge 5 commits intoNeVeSpl:mainfrom
Open
perf: net10.0 zero-alloc hot paths in NamespaceTree + BenchmarkDotNet project#13Rolling2405 wants to merge 5 commits intoNeVeSpl:mainfrom
Rolling2405 wants to merge 5 commits intoNeVeSpl:mainfrom
Conversation
Adds .NET 10 to library and test multi-targets while preserving netstandard2.0 (library) and net8.0 (tests). Fixes packaging icon declaration to use <None Include> (was <None Update>) so it resolves correctly under multi-targeting on newer SDKs. Workflow: - Bump actions/checkout v3 -> v4 - Bump actions/setup-dotnet v3 -> v4 - Add 10.0.x to dotnet-version (alongside 3.1.x and 8.0.x) Verified locally: 353 passed (2 skipped) on both net8.0 and net10.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…AOT smoke test Add [RequiresUnreferencedCode] and [RequiresDynamicCode] annotations to the dependency-search (Tier 2) public API surface, gated #if NET10_0_OR_GREATER, so that consumers using NativeAOT or the trimmer receive clear, actionable warnings at their own call sites rather than opaque linker errors. Changes ------- sources/NetArchTest/NetArchTest.csproj - Add net10.0 TFM alongside existing netstandard2.0 - Add IsTrimmable/IsAotCompatible guards (NET10_0_OR_GREATER only) sources/NetArchTest/NetArchTestAotMessages.cs (new) - Shared const message strings used by all Requires* attributes sources/NetArchTest/Condition_Dependencies.cs sources/NetArchTest/Predicate_Dependencies.cs sources/NetArchTest/Slices/SliceCondition.cs sources/NetArchTest/Functions/FunctionDelegates_Dependencies.cs - [RequiresUnreferencedCode] + [RequiresDynamicCode] on every public/internal method that drives the dependency-search pipeline (Tier 2 surface) sources/NetArchTest/Dependencies/TypeParser.cs - Type-level [UnconditionalSuppressMessage] for IL2057/IL2077/IL2080 covering Mono.Cecil private-field reflection in the static cctor - [RequiresUnreferencedCode] + [RequiresDynamicCode] on Parse() and ParseReflectionNameToRuntimeName() sources/NetArchTest/Dependencies/DataStructures/NamespaceTree.cs - Extract parseNames=true branch into private ParseTokensRequiringReflection() annotated with Requires*; Add() gets [UnconditionalSuppressMessage] with justification (runtime-guarded; Tier 1 callers pass false) sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs - [RequiresUnreferencedCode] + [RequiresDynamicCode] on ToType() sources/NetArchTest/Assemblies/PublicUse/TypeContainer.cs - Extract Lazy<Type> initialiser lambda into private ResolveReflectionType() annotated with Requires*; constructor gets [UnconditionalSuppressMessage] (Tier 1 paths never read ReflectionType) tests/NetArchTest.AotSmoke/ (new) - NativeAOT publish smoke that exercises Tier 1 only (Types.FromPath + name/namespace/structural predicates) - PublishAot=true, TreatWarningsAsErrors=true, NoWarn IL2104 (Mono.Cecil 0.11.x is not trim-annotated upstream — IL2104 is its aggregate warning; Tier 2 callers still get the proper annotations at their call sites) - Prints 'OK' and exits 0 when the AOT image is correct tests/NetArchTest.AotSmoke.Fixture/ (new) - Minimal test-assembly DLL published alongside the smoke binary Result: dotnet publish -r win-x64 -c Release produces a 4.3 MB native exe with zero IL warnings and zero errors; the exe prints 'OK' and exits 0. All 353x2 unit tests continue to pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Publishes tests/NetArchTest.AotSmoke with PublishAot=true on win-x64 and asserts the produced native exe exits 0. Runs on windows-latest since NativeAOT win-x64 cross-compilation requires the MSVC toolchain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Node: dual-mode FrozenDictionary (after Freeze) / mutable Dictionary (NET8_0_OR_GREATER) with CollectionsMarshal.GetValueRefOrAddDefault insert path (NET9_0_OR_GREATER) - SearchValues<char> SIMD separator scan in GetSubnameEndIndex (NET8_0_OR_GREATER) - Zero-allocation ReadOnlySpan<char> FrozenDictionary.GetAlternateLookup in TryGetNode overload and GetAllMatchingNames(string) inline-span path (NET9_0_OR_GREATER; avoids CS4007 iterator restriction) - NormalizeString: IsNormalized() fast-path to skip allocation for ASCII names - Constructor calls _root.Freeze() to lock the tree after build (NET8_0_OR_GREATER) - netstandard2.0 path byte-for-byte unchanged; all guards use NET*_OR_GREATER - Add NetArchTest.Benchmarks project (BenchmarkDotNet 0.14.0, net8.0;net10.0) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds tiered, backward-compatible performance optimizations to NamespaceTree — the core hot path called for every scanned type during dependency search — taking full advantage of .NET 8/9/10 APIs while keeping
etstandard2.0 entirely unchanged.
Approach: #if NET*_OR_GREATER tiers, no new TFMs
No new TFMs are added. The library already targets
etstandard2.0;net10.0. All new code is gated with #if NET8_0_OR_GREATER, #if NET9_0_OR_GREATER, or #if NET10_0_OR_GREATER so:
Optimizations by tier
Unconditional (all TFMs)
#if NET8_0_OR_GREATER
#if NET9_0_OR_GREATER
Files changed
et8.0;net10.0. [MemoryDiagnoser] included. Two public-API benchmarks over the dependency search hot path. [SimpleJob(RuntimeMoniker.Net80)] + [SimpleJob(RuntimeMoniker.Net100)] so the difference is directly measurable.
Test results
353 passed, 0 failed, 2 skipped on both
et8.0 and
et10.0.
Backward compatibility
etstandard2.0 compilation: zero changes (all new code is inside #if NET*_OR_GREATER blocks).