[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DZ0FQTEYAYC0ZB46SWB95W":3,"skill-same:01M6DZ0FQTEYAYC0ZB46SWB95W":79},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":49,"commitSha":50,"skillDirectory":51,"forksCount":52,"repositoryHtmlUrl":53,"repositoryHomepageUrl":54,"ownerHtmlUrl":55,"ownerBlogUrl":56,"ownerDescription":57,"updatedAt":58,"install":59,"resources":72},"01M6DZ0FQTEYAYC0ZB46SWB95W","blazing-fast-dotnet",null,"High-performance .NET guidance for writing and tuning C# apps and benchmarks (BenchmarkDotNet, ASP.NET, challenges). Use when asked to make .NET code faster, reduce allocations or GC overhead, fix lock contention or thread pool starvation, inspect JIT codegen, profile apps, or tune runtime\u002FNativeAOT settings.","managedcode\u002Fdotnet-skills","managedcode","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F78253589?v=4","01M6DYDJR3EG8KB5G4PC65XCTT","Managed Code","01M6DYDJX0ZT432N04NYJW89DN",512,"MIT","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FTools\u002FBlazingFastDotnet\u002Fskills\u002Fblazing-fast-dotnet\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"blazing fast dotnet","fast","dotnet","for","make","overhead","lock","reduce","when","inspect","profile","jit","guidance","tuning","settings","gc","benchmarks","code","apps","high-performance","use","writing","fix","performance","runtime","2026-11-10 10:12:30",false,"---\nname: blazing-fast-dotnet\ndescription: High-performance .NET guidance for writing and tuning C# apps and benchmarks (BenchmarkDotNet, ASP.NET, challenges). Use when asked to make .NET code faster, reduce allocations or GC overhead, fix lock contention or thread pool starvation, inspect JIT codegen, profile apps, or tune runtime\u002FNativeAOT settings.\n---\n\n# Blazing Fast .NET Performance\n\n> [!WARNING]\n> Much of the advice here might not age well: as .NET evolves, old hacks and custom knobs may become obsolete or even detrimental. Apply only after careful consideration and testing. Revisit periodically to ensure continued relevance.\n\nThis skill focuses on optimizing .NET applications for maximum performance, providing tips, techniques, and best practices to achieve blazing-fast execution times. It targets .NET 10.0 and 11.0. Older versions have not been tested.\n\n**Contents**\n\n- [Profiling and Performance Analysis](#profiling-and-performance-analysis)\n  - [dotnet-counters](#dotnet-counters)\n  - [dotnet-trace](#dotnet-trace)\n  - [Low-level profiling with Linux `perf`](#low-level-profiling-with-linux-perf)\n  - [Popular low-level problems](#popular-low-level-problems)\n  - [Inspecting JIT codegen](#inspecting-jit-codegen)\n- [Best practices for high-performance C# code](#best-practices-for-high-performance-c-code)\n  - [Quick tips](#quick-tips)\n  - [Strings and globalization](#strings-and-globalization)\n  - [Thread pool starvation](#thread-pool-starvation)\n  - [Contention on shared state](#contention-on-shared-state)\n    - [False sharing](#false-sharing)\n  - [Safe vectorization (SIMD or SWAR)](#safe-vectorization-simd-or-swar)\n  - [Generic specialization via static abstract members](#generic-specialization-via-static-abstract-members)\n- [ASP.NET Core](#aspnet-core)\n  - [Hosting and logging](#hosting-and-logging)\n  - [JSON and response writing](#json-and-response-writing)\n  - [Routing and middleware](#routing-and-middleware)\n  - [Kestrel socket transport](#kestrel-socket-transport)\n  - [Beyond benchmarks](#beyond-benchmarks)\n- [Magic knobs to Tune .NET Performance](#magic-knobs-to-tune-net-performance)\n- [NativeAOT-specific tuning](#nativeaot-specific-tuning)\n- [General notes](#general-notes)\n\n# Profiling and Performance Analysis\n\nStart cheap and go deeper only when needed: counters (what kind of bottleneck?) → traces (where?) → `perf` and JIT disassembly.\nInstall the tools with:\n\n```sh\ndotnet tool install -g dotnet-counters\ndotnet tool install -g dotnet-trace\ndotnet tool install -g dotnet-stack\ndotnet tool install -g dotnet-symbol\n```\n\n## dotnet-counters\n\n[dotnet-counters](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fdiagnostics\u002Fdotnet-counters) is a low-overhead, first-level triage tool. Run it while the app is under load:\n\n```sh\ndotnet-counters monitor -p \u003Cpid>                                    # System.Runtime counters by default\ndotnet-counters monitor -p \u003Cpid> --counters System.Runtime,Microsoft.AspNetCore.Hosting,System.Net.Http\ndotnet-counters collect -p \u003Cpid> --format csv -o counters.csv      # save for post-processing\n```\n\nSee the `System.Runtime` [built-in metrics](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fdiagnostics\u002Fbuilt-in-metrics-runtime) (.NET 9+) for what each counter means. Typical readings:\n\n| Symptom | Counters | Next step |\n| --- | --- | --- |\n| Low CPU, rising latency | `dotnet.thread_pool.queue.length` grows, `dotnet.thread_pool.thread.count` keeps creeping up | [Thread pool starvation](#thread-pool-starvation) |\n| Throughput doesn't scale with cores | `dotnet.monitor.lock_contentions` rate | `dotnet-trace --clrevents contention`, [Contention on shared state](#contention-on-shared-state) |\n| High CPU, much of it in GC | `dotnet.gc.pause.time`, `dotnet.gc.collections` (gen2 share), `dotnet.gc.heap.total_allocated` rate | [Allocations and GC](#allocations-and-gc) |\n| Unexplained slow requests | `dotnet.exceptions` rate | `dotnet-trace --clrevents exception` |\n| Numbers still drifting | `dotnet.jit.compiled_methods` still growing | Not at steady state yet: warm up longer |\n\n## dotnet-trace\n\n[dotnet-trace](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fdiagnostics\u002Fdotnet-trace) collects EventPipe traces on any OS without root or a native profiler (managed frames only):\n\n```sh\ndotnet-trace collect -p \u003Cpid> --duration 00:00:00:10    # default profiles: dotnet-common + dotnet-sampled-thread-time\ndotnet-trace collect -p \u003Cpid> --profile gc-verbose      # GCs + sampled allocations: which types\u002Fstacks allocate\ndotnet-trace collect -p \u003Cpid> --clrevents contention    # lock contention events with stacks\ndotnet-trace collect -p \u003Cpid> --providers Microsoft-Windows-DotNETRuntime:0x80000000000:4  # .NET 10+: randomized allocation sampling, cheaper than gc-verbose\ndotnet-trace collect -- .\u002FMyApp arg1                    # trace from startup\ndotnet-trace list-profiles\n\ndotnet-trace report trace.nettrace topN -n 20           # top methods (exclusive) as plain text\ndotnet-trace report trace.nettrace topN -n 20 --inclusive\ndotnet-trace convert trace.nettrace --format Speedscope # open in https:\u002F\u002Fwww.speedscope.app\n```\n\nOn Linux with kernel 6.4+ and root, `dotnet-trace collect-linux` (.NET 10+) records native, kernel and managed frames in one trace without the `perf` setup below.\n\nFor more tips on using `dotnet-trace`, refer to the [dotnet-trace collection skill](https:\u002F\u002Fraw.githubusercontent.com\u002Fdotnet\u002Fskills\u002Frefs\u002Fheads\u002Fmain\u002Fplugins\u002Fdotnet-diag\u002Fskills\u002Fdotnet-trace-collect\u002FSKILL.md).\n\n## Low-level profiling with Linux `perf`\n\nThe instructions below are mostly for CoreCLR\u002FJIT scenarios on Linux with the native `perf` tool. NativeAOT\u002FR2R scenarios should presumably work as is, without `perf inject`.\n\nTypically, the following environment variables need to be set in the context of the running application:\n\n```sh\nDOTNET_PerfMapEnabled=1\nDOTNET_PerfMapStubGranularity=3\nDOTNET_JitFramed=1\nDOTNET_EnableWriteXorExecute=0\n```\n\n`DOTNET_PerfMapEnabled` has an overhead of its own: set it for the profiling run only, not for the runs you take throughput numbers from.\n\nWhat to profile depends on the scenario. Typically it's steady-state performance, so wait until the process is properly warmed up (e.g. 1 minute), then attach and collect traces:\n\n```sh\nperf record -k 1 -g -F 3999 -p $PROCESS_PID -o perf.data -- sleep 5\n```\nNext, symbolize the collected JIT frames using `perf inject`:\n\n```sh\nperf inject --input perf.data --jit --output perfjit.data\n```\n\nThen use the resulting `perfjit.data` file as needed, e.g. to generate flame graphs or annotated asm:\n\n```sh\n# annotated asm & report:\nperf report --input perfjit.data --no-children --percent-limit 1 --stdio > perf_report.txt\nperf annotate --stdio2 -i perfjit.data --percent-limit 1 -M intel > perf.asm\n\n# Generate flamegraph (svg)\ngit clone --depth 1 --quiet https:\u002F\u002Fgithub.com\u002Fbrendangregg\u002FFlameGraph || true\nperf script -i perfjit.data | .\u002FFlameGraph\u002Fstackcollapse-perf.pl | .\u002FFlameGraph\u002Fflamegraph.pl > flamegraph.svg\n```\n\nNow `perf.asm` contains the annotated assembly output for the hottest methods in your application. Keep in mind that sample attribution is imprecise (\"skid\"): the cost is often reported on an instruction shortly after the one that actually stalls (e.g. right after a slow load or division), so inspect the neighboring instructions too.\n\nIn case of missing symbols you may need to use `dotnet-symbol` to download the necessary debug symbols for .dll\u002F.dylib\u002F.so:\n\n```sh\ndotnet-symbol --symbols $PUBLISH_FOLDER\u002F*.dll\ndotnet-symbol --symbols $PUBLISH_FOLDER\u002F*.so\n```\n\nAll other `perf`-related techniques (e.g. c2c, branch profiling, cache misses) can be applied similarly, depending on the performance aspects you want to analyze.\n\nFor simple functions, create a self-contained console application that calls the function in an infinite loop. For BenchmarkDotNet, use the in-process toolchain (`-i` or `[InProcess]`) with a long iteration duration and a very large iteration count so it is clear which process to attach `perf` to.\n\n## Popular low-level problems\n\n- Cache contention and false sharing in multi-threaded code. Fix with per-thread data and padding (see [False sharing](#false-sharing)).\n- Bounds checks not being elided by the JIT.\n- Store-to-load forwarding stalls, e.g. writing a struct field by field and reading it back with a wider load.\n- Bad data locality causing cache misses.\n- Code alignment noise. Loop or branch placement, Intel JCC erratum - not something we can control, just to keep in mind when analyzing performance variations.\n- GC's alignment is 8 bytes (64-bit), which might not be suitable for SIMD optimizations (e.g. AVX512 might penalize every load\u002Fstore if misaligned since the cache line is 64 bytes).\n\n## Inspecting JIT codegen\n\nThese are diagnostic switches (not tuning knobs) and work with the shipped Release runtime:\n\n```sh\nDOTNET_JitDisasm=\"Test\"            # method list: 'Test', 'Program:Test', '*Test*', 'Program:*' (space-separated)\nDOTNET_JitDisasmSummary=1          # list every jitted method with its tier\nDOTNET_JitStdOutFile=\u002Ftmp\u002Fjit.txt  # write JIT output to a file instead of stdout\nDOTNET_JitDisasmDiffable=1         # stable output (no addresses) for before\u002Fafter diffs\nDOTNET_JitDisasmOnlyOptimized=1    # show only Tier-1\u002FFullOpts code\n```\n^ might require `MethodImplOptions.NoInlining` on the method to ensure it gets JIT-compiled and not inlined away.\n\n> [!TIP]\n> Set them only for the app process, not for `dotnet run`, which would also dump the SDK's own methods.\n\n- A method is compiled several times as it tiers up. Judge the last version: header `; Tier1 code` (or `; Tier1-OSR code` \u002F `; FullOpts code`), ideally with `; optimized using Dynamic PGO`. `; Tier0 code` and `; Instrumented Tier0 code` are unoptimized.\n- BCL methods precompiled with ReadyToRun show up only once they get hot and are re-jitted at Tier1.\n- NativeAOT: pass the same options to ILC, e.g. `\u003CIlcArg Include=\"--codegenopt:JitDisasm=Test\" \u002F>` and `\u003CIlcArg Include=\"--codegenopt:JitStdOutFile=jit.txt\" \u002F>`.\n- Inlining decisions: `DOTNET_JitPrintInlinedMethods` needs a Checked JIT. On the Release runtime, collect the `MethodJitInliningSucceeded`\u002F`MethodJitInliningFailed` events (with the reason) via `dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:0x1000:5`.\n\n# Best practices for high-performance C# code\n\n> [!IMPORTANT]\n> Only safe C# code is considered in these best practices. Unsafe code is highly discouraged and not covered here.\n\n## Quick tips\n\n- Use [source-generated System.Text.Json](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fserialization\u002Fsystem-text-json\u002Fsource-generation): pass generated `JsonTypeInfo\u003CT>` to avoid reflection-based metadata discovery and enable supported serialization fast paths. Preserve existing serialization options and behavior.\n- Use [`[LoggerMessage]`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fextensions\u002Flogging\u002Fsource-generation) source-generated logging to avoid template parsing, boxing, and temporary allocations. Still guard expensive argument computation with `IsEnabled`.\n- Use [`[GeneratedRegex]`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fbase-types\u002Fregular-expression-source-generators) for compile-time-known patterns instead of runtime pattern compilation. Preserve options, culture, and match timeouts.\n- Prefer optimized bulk APIs over handwritten loops: `Span.CopyTo`, `Clear`, `Fill`, `SequenceEqual`, and `IndexOf` already have specialized implementations, including SIMD where appropriate.\n- Use RVA-backed spans `static ReadOnlySpan\u003Clong> RVA => [1, 2, 3];` for immutable data (only primitives, e.g., `long`, `byte`, `char`) - the data will be embedded into metadata, and the JIT will be able to perform constant folding on it.\n- Prefer static lambdas and state-passing overloads in hot paths to avoid capturing closures.\n- Parse and format UTF-8 directly: numeric `TryParse(ReadOnlySpan\u003Cbyte>, ...)` and `TryFormat(Span\u003Cbyte>, ...)` avoid intermediate strings and UTF-8\u002FUTF-16 transcoding. Preserve culture and formatting semantics.\n- Cache `SearchValues\u003Cchar>` \u002F `SearchValues\u003Cbyte>` for repeated searches against fixed sets; use `SearchValues\u003Cstring>` for multi-substring searches. Construction has a cost, so do not create these per call.\n- Consider `FrozenDictionary` \u002F `FrozenSet` for build-once, read-many data. Their construction cost must be amortized over enough lookups; preserve the intended comparer.\n- Use dictionary alternate lookups from spans: `dictionary.GetAlternateLookup\u003CReadOnlySpan\u003Cchar>>()` avoids allocating temporary string keys when the comparer supports it. Use `TryGetAlternateLookup` when comparer support is not known.\n- Implement `IEquatable\u003CT>` and a good `GetHashCode` for struct dictionary\u002Fset keys. This can avoid expensive default equality paths, boxing, and collision-heavy hashes.\n- Try [`TensorPrimitives`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fapi\u002Fsystem.numerics.tensors.tensorprimitives) before hand-vectorizing numeric operations. Its span-based implementations handle SIMD and tails; prefer it over manual SIMD loops or LINQ for things like finding min\u002Fmax, sums, etc.\n- Prefer [`[LibraryImport]`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fnative-interop\u002Fpinvoke-source-generation) over `[DllImport]` for supported signatures to generate marshalling at build time; both `DllImport` and `LibraryImport` require `AllowUnsafeBlocks=true` under unsafe-v2, but if interop is not avoidable, `LibraryImport` is generally preferred.\n- Use `static readonly` flags for genuinely immutable process-wide configuration. Optimized JIT code can fold their values and eliminate disabled branches.\n- Cache fields in locals in hot loops (`var items = _items;`). The JIT often can't do it for you: calls and stores through references may modify the field, and the memory model restricts hoisting reads. Locals also enable bounds check elimination for arrays\u002Fspans.\n- Consider `Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping` for UTF-8 JSON-only responses ([documentation](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fserialization\u002Fsystem-text-json\u002Fcharacter-encoding#serialize-all-characters)). Never embed its raw output in HTML or `\u003Cscript>`; any custom encoder disables STJ's generated serialization fast path, so benchmark rather than assuming the gains add up.\n- Create `JsonSerializerOptions` once and reuse it (CA1869). A new instance with the same settings still pays a global cache lookup; one that also creates converter instances (`Converters = { new JsonStringEnumConverter() }`) misses that cache, since converters compare by reference, and rebuilds metadata for every type it touches on each call.\n- For most bit\u002Fbyte manipulations, prefer the built-in `BitOperations`, `BinaryPrimitives`, `BitConverter` or static numeric helpers such as `uint.Log2`, `uint.PopCount`, `uint.TrailingZeroCount`, etc.\n- Span\u002FReadOnlySpan have a lot of useful extension methods via `MemoryExtensions` such as `SequenceEqual`, `IndexOf`, `IndexOfAnyExcept`, `LastIndexOf`, `StartsWith`, `CommonPrefixLength`, and a lot more - prefer these APIs over manual loops or vectorization.\n- Don't pin benchmarks to a single core - doing so might significantly affect internal VM heuristics (e.g. tiered compilation timing).\n- For BenchmarkDotNet methodology, see the [dotnet\u002Fruntime microbenchmark skill](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fmicrobenchmark\u002FSKILL.md).\n- `AggressiveOptimization` is usually a deoptimization.\n- Overusing `AggressiveInlining` may hurt performance (e.g. by exhausting the inliner's time budget or increasing the number of tracked local variables).\n- Moving rarely taken paths (grow\u002Fresize, slow fallback) into a separate method marked with  `[MethodImpl(MethodImplOptions.NoInlining)]` may improve performance (as `List\u003CT>.Add` does with `AddWithResize`), but it may also hinder escape analysis.\n- Don't put too much data into TLS (Thread-Local Storage) - excessive use can lead to increased memory usage and cache pressure, reducing overall performance (there can be a lot of threads, each with its own copy).\n- Consider `CollectionsMarshal.AsSpan(list)` for `List\u003CT>` (there is no `List\u003CT>.AsSpan()`), or `.AsSpan()` on collections that support it, to avoid unnecessary overhead from bound and covariance checks, and to enable span-based algorithms directly on the collection's underlying memory. Don't add or remove list items while the span is in use.\n- Prefer XXH3 via Microsoft's `System.IO.Hashing.XxHash3` for fast fingerprinting of large blobs. Use `XxHash128` when 128-bit fingerprints are needed. For exact deduplication, verify matches byte for byte. Use cryptographic hashes when security requires them. NOTE: Be careful with data that may contain garbage or uninitialized bytes (e.g. arrays of structs with padding).\n- Split without allocating: `foreach (Range r in span.Split(','))` (.NET 9+) or `span.Split(destinationRanges, ',')`, instead of `string.Split` plus `Substring`.\n- Use `System.Threading.Lock` (.NET 9+) rather than `object` for new `lock` targets.\n- For tiny loop bodies, `Parallel.For` pays a delegate call per element. Iterate ranges instead: `Parallel.ForEach(Partitioner.Create(0, n), range => { for (int i = range.Item1; i \u003C range.Item2; i++) ... })`.\n- `Console.Out` flushes after every write. For heavy output, write through one buffered `Stream`\u002F`StreamWriter` over `Console.OpenStandardOutput()` and flush once at the end.\n- To read a large file from several threads, use `File.OpenHandle` + `RandomAccess.Read(handle, buffer, fileOffset)`: offset-based, thread-safe, no shared position and no `FileStream` buffering layer.\n\n## Strings and globalization\n\n- Prefer ordinal operations. Culture-sensitive comparisons go through ICU, are many times slower than ordinal ones, and are often a correctness bug anyway.\n- Culture-sensitive by default: `string.Compare`, `CompareTo`, `StartsWith(string)`, `EndsWith(string)`, `IndexOf(string)`, `LastIndexOf(string)`, `ToUpper()`, `ToLower()`, and default string sorting (`Array.Sort`, `List\u003CT>.Sort`, `OrderBy`, `SortedDictionary`\u002F`SortedSet` with the default comparer). Pass `StringComparison.Ordinal`\u002F`OrdinalIgnoreCase` or `StringComparer.Ordinal`; analyzers CA1307, CA1309 and CA1310 find these calls.\n- Already ordinal: `==`, `Equals`, `Contains(string)`, `Replace(string, string)`, `IndexOf(char)`, `Split`, `GetHashCode`, and the default `Dictionary`\u002F`HashSet` comparer.\n- Case-insensitive equality: `string.Equals(a, b, StringComparison.OrdinalIgnoreCase)` instead of `a.ToLower() == b.ToLower()` (two allocations plus culture rules). For ASCII data, `System.Text.Ascii` has vectorized `EqualsIgnoreCase`, `ToUpper`\u002F`ToLower` (span to span) and `IsValid`.\n- `Dictionary\u003Cstring, T>` \u002F `HashSet\u003Cstring>` use fast non-randomized hashing only with the default comparer, `StringComparer.Ordinal` or `StringComparer.OrdinalIgnoreCase` (falling back to randomized hashing only on heavy collisions). Any other comparer loses it.\n- Stay in UTF-8 when input and output are UTF-8: search, parse and format `ReadOnlySpan\u003Cbyte>` directly (`u8` literals, `SearchValues\u003Cbyte>`, `TryParse`\u002F`TryFormat` on bytes, `Utf8.TryWrite`) and convert to `string` only when needed.\n- If the app needs no culture data, `\u003CInvariantGlobalization>true\u003C\u002FInvariantGlobalization>` removes the ICU dependency (faster startup, less memory) and makes culture-sensitive string operations ordinal. This changes behavior, so verify the output.\n\n## Thread pool starvation\n\nSymptoms: low CPU and rising latency while `dotnet.thread_pool.queue.length` grows and `dotnet.thread_pool.thread.count` creeps up (beyond the minimum, the pool adds threads slowly). The cause is pool threads blocked instead of running work: sync-over-async (`.Result`, `.Wait()`, `GetAwaiter().GetResult()`), synchronous I\u002FO, or locks held across slow operations.\n\n```sh\ndotnet-stack report -p \u003Cpid>   # all managed stacks: many threads parked in the same Wait\u002FGetResult frame is the culprit\ndotnet-trace collect -p \u003Cpid> --providers Microsoft-Windows-DotNETRuntime:0x40000000000:5  # .NET 9+: wait events with stacks\n```\n\nThe fix is to make the blocked path async end to end; wrapping it in `Task.Run` only moves the blocked thread. As a stopgap, raise the minimum worker count with `ThreadPool.SetMinThreads` or `DOTNET_ThreadPool_ForceMinWorkerThreads` (see the knobs table).\n\n## Contention on shared state\n\nWrites to one shared location from many threads (`Interlocked.Increment` on a global counter, a shared \"total\" field, etc.) serialize all of them on a single cache line, so throughput can get *worse* as cores are added. Split the state, update only the local part, and combine on read. Two options:\n\n**1. Per-core stripes** - index an array by `Thread.GetCurrentProcessorId()` (cheap: cached and periodically refreshed). The id is only a hint: threads migrate, and ids can exceed `ProcessorCount` (hence the modulo), so keep `Interlocked` on the stripe - it's almost always uncontended. Fixed memory and allocation-free reads; works with any number of threads (thread pool, request handlers). Stripes must be padded, see [False sharing](#false-sharing).\n\n```cs\nsealed class StripedCounter\n{\n    private readonly PaddedLong[] _stripes = new PaddedLong[Environment.ProcessorCount];\n\n    public void Increment()\n    {\n        PaddedLong[] stripes = _stripes;\n        Interlocked.Increment(ref stripes[(uint)Thread.GetCurrentProcessorId() % (uint)stripes.Length].Value);\n    }\n\n    \u002F\u002F Not an atomic snapshot, which is fine for statistics.\n    public long Sum()\n    {\n        PaddedLong[] stripes = _stripes;\n        long sum = 0;\n        for (int i = 0; i \u003C stripes.Length; i++)\n            sum += Volatile.Read(ref stripes[i].Value);\n        return sum;\n    }\n}\n```\n\n**2. `ThreadLocal\u003CT>` with `trackAllValues: true`** - each thread updates its own slot with no atomics at all; sum `Values` on read. `Values` allocates a list and includes values of threads that have already exited, and every thread that ever touched it keeps a slot, so this suits a bounded set of long-lived threads (e.g. `Parallel` workers) where writes vastly outnumber reads. Dispose the `ThreadLocal\u003CT>` when done.\n\n```cs\nprivate readonly ThreadLocal\u003Clong> _count = new(trackAllValues: true);\n\nvoid OnItem() => _count.Value++;\nlong Total() => _count.Values.Sum();\n```\n\nIf the work structure allows it, aggregating into plain locals and combining once per task\u002Fpartition (`Parallel.For` with `localInit`\u002F`localFinally`) is cheaper than both.\n\n### False sharing\n\nThreads writing *different* variables still contend if those variables share a cache line (64 bytes on most x64\u002Farm64 CPUs, 128 bytes on Apple M-series): every write invalidates the line in the other cores' caches. The naive striped counter is the classic case:\n\n```cs\n\u002F\u002F 8 longs per 64-byte line: the per-core slots look independent, but cores keep stealing the line from each other.\nlong[] stripes = new long[Environment.ProcessorCount];\nInterlocked.Increment(ref stripes[(uint)Thread.GetCurrentProcessorId() % (uint)stripes.Length]);\n```\n\nFix it by padding each slot so no two hot values can land on the same line:\n\n```cs\nstruct PaddedLong\n{\n    public long Value;\n#pragma warning disable CS0169 \u002F\u002F padding only\n    private InlineArray8\u003Clong> _padding; \u002F\u002F 64 bytes: adjacent Values are 72 bytes apart\n#pragma warning restore CS0169\n}\n```\n\n- Most server hardware (all x64 CPUs and Neoverse-based arm64 CPUs such as Cobalt and Graviton) has 64-byte cache lines, so 64 bytes of padding is usually sufficient. Apple M-series CPUs have 128-byte cache lines, so consider padding to 128 bytes there.\n- The same applies to fields of one object written by different threads (e.g. a ring buffer's head and tail indices): put a padding field between them.\n\n## Safe vectorization (SIMD or SWAR)\n\nIn .NET 11.0 the JIT got improvements in bounds check elimination and now recognizes the following shape for safely loading and processing multiple elements from an array\u002Fspan:\n\n```cs\nwhile (data.Length >= CNS)\n{\n    \u002F\u002F JIT knows that the slice has at least CNS elements, so bounds checks can be elided.\n    \u002F\u002F e.g.:\n    \u002F\u002F  VectorX.Create(data);\n    \u002F\u002F  BitConverter.ToInt32(data);\n    \u002F\u002F  BinaryPrimitives.ReadInt32LittleEndian(data);\n    \u002F\u002F  etc.\n    data = data.Slice(CNS);\n}\n```\n\nFor example, the following C# snippet has no bounds checks in .NET 11.0:\n\n```cs\nint Sum(Span\u003Cint> data)\n{\n    Vector128\u003Cint> sum = default;\n    while (data.Length >= Vector128\u003Cint>.Count)\n    {\n        sum += Vector128.Create(data);\n        data = data.Slice(Vector128\u003Cint>.Count);\n    }\n    int result = Vector128.Sum(sum);\n    foreach (var t in data)\n        result += t;\n    return result;\n}\n```\n\n.NET 12.0 (Preview 1 or main) extends it to more complex loop shapes (e.g. multiple spans):\n\n```cs\nstatic bool Equal(ReadOnlySpan\u003Cushort> a, ReadOnlySpan\u003Cushort> b)\n{\n    if (a.Length != b.Length)\n        return false;\n\n    int i = 0;\n    if (Vector128.IsHardwareAccelerated)\n    {\n        for (; i \u003C= a.Length - Vector128\u003Cushort>.Count; i += Vector128\u003Cushort>.Count)\n        {\n            var va = Vector128.Create(a.Slice(i));\n            var vb = Vector128.Create(b.Slice(i));\n            if (va != vb)\n                return false;\n        }\n    }\n\n    for (; i \u003C a.Length; i++)\n    {\n        if (a[i] != b[i])\n            return false;\n    }\n\n    return true;\n}\n```\n^ has no bounds checks for the main loop in .NET 12.0 (Preview 1).\n\n> [!TIP]\n> This only illustrates the loop shape. For this exact task, just call `a.SequenceEqual(b)`.\n\nMore info about vectorization and SIMD in .NET can be found in the [official documentation](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fdotnet\u002Fstandard\u002Fsimd). Also consider using [TensorPrimitives](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fapi\u002Fsystem.numerics.tensors.tensorprimitives). The [dotnet\u002Fruntime vectorization skill](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fvectorization\u002FSKILL.md) has authoring and testing checklists (remainder handling, running with `DOTNET_EnableAVX2=0` \u002F `DOTNET_EnableHWIntrinsic=0` to cover every path); the `unsafe` load\u002Fstore variants it mentions are out of scope here.\n\n## Generic specialization via static abstract members\n\nStatic abstract interface members let you specialize a generic method for values supplied by struct tags, without duplicating its source code. The JIT generates separate native versions of `Big\u003CC42>` and `Big\u003CC100>`. In optimized code, it can inline the small `Value` getters and fold expressions and branches that use `T.Value`. This works even when `Big\u003CT>` itself is too large to inline; The tradeoff is increased native code size. `T.Value` is not a C# compile-time constant: constant folding depends on JIT optimizations and getter inlining.\n\n```cs\nusing System.Runtime.CompilerServices;\n\ninterface IConstant {\n    static abstract int Value { get; }\n}\n\nreadonly struct C42 : IConstant {\n    public static int Value => 42;\n}\n\nreadonly struct C100 : IConstant {\n    public static int Value => 100;\n}\n\nstatic class Example\n{\n    static int Big\u003CT>(int x) where T : struct, IConstant\n    {\n        int arg = T.Value;\n\n        \u002F\u002F The large body would go here.\n\n        return arg == 42 ? x * arg : x + arg;\n    }\n\n    static int Use42(int x) => Big\u003CC42>(x);\n    static int Use100(int x) => Big\u003CC100>(x);\n}\n```\n\nIt is also handy for scenarios where you want to leverage different vector sizes or constants in a generic way, allowing the JIT to generate optimized code for each specialization without duplicating the method's source code. Example: summing an array using different vector widths with static abstract members.\n\n```cs\nusing System;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.Intrinsics;\n\nclass Program\n{\n    static void Main()\n    {\n        int[] values = new int[1003];\n\n        if (Vector128.IsHardwareAccelerated)\n            Console.WriteLine(Sum\u003CVector128\u003Cint>, Ops128>(values));\n        if (Vector256.IsHardwareAccelerated)\n            Console.WriteLine(Sum\u003CVector256\u003Cint>, Ops256>(values));\n        if (Vector512.IsHardwareAccelerated) \n            Console.WriteLine(Sum\u003CVector512\u003Cint>, Ops512>(values));\n    }\n\n    [MethodImpl(MethodImplOptions.NoInlining)]\n    static int Sum\u003CV, TOps>(ReadOnlySpan\u003Cint> s)\n        where V : struct\n        where TOps : struct, IOps\u003CV>\n    {\n        V acc = default;\n        int count = TOps.Count;\n\n        while (s.Length >= count)\n        {\n            acc = TOps.Add(acc, TOps.Load(s));\n            s = s.Slice(count);\n        }\n\n        int sum = TOps.Sum(acc);\n        foreach (int x in s) sum = unchecked(sum + x);\n        return sum;\n    }\n}\n\n\ninterface IOps\u003CV> where V : struct\n{\n    static abstract int Count { get; }\n    static abstract V Load(ReadOnlySpan\u003Cint> s);\n    static abstract V Add(V a, V b);\n    static abstract int Sum(V v);\n}\n\nreadonly struct Ops128 : IOps\u003CVector128\u003Cint>>\n{\n    public static int Count => Vector128\u003Cint>.Count;\n    public static Vector128\u003Cint> Load(ReadOnlySpan\u003Cint> s) => Vector128.Create(s);\n    public static Vector128\u003Cint> Add(Vector128\u003Cint> a, Vector128\u003Cint> b) => a + b;\n    public static int Sum(Vector128\u003Cint> v) => Vector128.Sum(v);\n}\n\nreadonly struct Ops256 : IOps\u003CVector256\u003Cint>>\n{\n    public static int Count => Vector256\u003Cint>.Count;\n    public static Vector256\u003Cint> Load(ReadOnlySpan\u003Cint> s) => Vector256.Create(s);\n    public static Vector256\u003Cint> Add(Vector256\u003Cint> a, Vector256\u003Cint> b) => a + b;\n    public static int Sum(Vector256\u003Cint> v) => Vector256.Sum(v);\n}\n\nreadonly struct Ops512 : IOps\u003CVector512\u003Cint>>\n{\n    public static int Count => Vector512\u003Cint>.Count;\n    public static Vector512\u003Cint> Load(ReadOnlySpan\u003Cint> s) => Vector512.Create(s);\n    public static Vector512\u003Cint> Add(Vector512\u003Cint> a, Vector512\u003Cint> b) => a + b;\n    public static int Sum(Vector512\u003Cint> v) => Vector512.Sum(v);\n}\n```\n\n# ASP.NET Core\n\nCode-level advice for HTTP throughput benchmarks. To run the TechEmpower apps against a locally built runtime, see the [dotnet\u002Fruntime aspnet-benchmark skill](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Faspnet-benchmark\u002FSKILL.md).\n\n## Hosting and logging\n\n- Start from `WebApplication.CreateSlimBuilder(args)` (or `CreateEmptyBuilder` + `builder.WebHost.UseKestrelCore()`) and add only what's needed. Every middleware component (HTTPS redirection, auth, CORS, response compression, static files, developer exception page) costs something per request. The slim builder drops HTTPS configuration, HTTP\u002F3, regex route constraints, IIS integration and hosting startup assemblies, but still reads `appsettings.json` and still adds a console logger.\n- Logging: without an `appsettings.json` that raises `Microsoft.AspNetCore` to `Warning`, the default level is `Information` and the host logs every request to the console - a massive bottleneck. Raising the level removes the log writes, but while any provider is registered the host still creates an `Activity` and a logging scope per request; `builder.Logging.ClearProviders()` removes those too.\n- Measure in the `Production` environment. `Development` turns on DI `ValidateScopes`\u002F`ValidateOnBuild`, and `dotnet run` picks it up from `launchSettings.json`: run the published output, or `dotnet run -c Release --no-launch-profile`.\n\n## JSON and response writing\n\n- JSON: register source-generated metadata with `builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));`. For hand-written responses, serialize with a reused `Utf8JsonWriter` (`writer.Reset(response.BodyWriter)`) instead of allocating one per request.\n- Make response DTOs `sealed` (or structs). A minimal API handler returning a sealed `T` gets a direct serialization path chosen once when the endpoint is built; for a non-sealed class the framework calls `GetType()` on every response and falls back to serializing as `object` when the runtime type differs.\n- On the hottest endpoints return `T` itself. `TypedResults.Ok(value)` and `Results.Json(value)` do a type-info lookup and a `GetType()` check per call even for sealed types.\n- `WriteAsJsonAsync` never sets `Content-Length`, and Kestrel only fills it in for empty bodies, so HTTP\u002F1.1 responses go out chunked. When the exact size is known up front, set `Response.ContentLength` (a mismatch is an error).\n- Static payloads: keep a pre-encoded `static readonly byte[]`, set `ContentLength`, write once. `Results.Text(utf8Span, ...)` copies the span into a new array on each call; the result object is immutable, so create it once in a static field. Don't pass `statusCode` to it on hot paths: that resolves `ILoggerFactory` and creates a logger per request.\n- Set headers through the typed properties (`response.Headers.ContentType = ...`, analyzer ASP0015): on Kestrel they are direct field writes, while the string indexer goes through a name lookup.\n\n## Routing and middleware\n\n- A handler that is a plain `RequestDelegate` (`app.MapGet(\"\u002Fx\", (HttpContext ctx) => ...)` returning `Task`) bypasses parameter binding and the generated request delegate entirely. If such a lambda returns `Task\u003CT>`, the value is silently discarded (analyzer ASP0016).\n- Inline middleware: use `app.Use(async (context, next) => { ...; await next(context); })`. The `next()` form (`Func\u003CTask>`) allocates a closure per request.\n- Prefer convention-based middleware classes (constructed once) over `IMiddleware`, which is resolved from DI and released on every request.\n- `.ShortCircuit()` on an endpoint (or `app.MapShortCircuit(404, \"favicon.ico\")`) runs it inside the routing middleware and skips the rest of the pipeline. It throws if the endpoint has auth, CORS or antiforgery metadata.\n- Avoid `IHttpContextAccessor` in hot services: once registered (ASP.NET Core Identity does it for you), an `AsyncLocal` is set on every request. Pass `HttpContext` explicitly.\n\n## Kestrel socket transport\n\nConfigure with `builder.WebHost.UseSockets(o => ...)`:\n\n- `WaitForDataBeforeAllocatingBuffer = false` trades memory per idle connection for throughput.\n- `IOQueueCount` sets the number of I\u002FO queues that socket continuations are scheduled on (default is based on the processor count); `0` schedules them directly on the thread pool.\n\n## Beyond benchmarks\n\n- Output caching (`AddOutputCache`, `.CacheOutput()`): the default policy caches only unauthenticated `GET`\u002F`HEAD` 200 responses without `Set-Cookie`.\n- Serve static files with `MapStaticAssets` rather than `UseStaticFiles`: precompressed variants and ETags are computed at build time.\n- Response compression is CPU per request and is off for HTTPS by default; prefer precompressed assets or the reverse proxy.\n- `-p:EnableRequestDelegateGenerator=true` moves minimal API handler codegen from startup to build time (startup win; on by default with `PublishAot` or `PublishTrimmed`).\n\n# Magic knobs to Tune .NET Performance\n\n> [!WARNING]\n> It is highly recommended to keep the default settings unless you have measured and understood the performance implications of changing them. \n\nMost of these knobs are relevant only for server workloads or performance-critical scenarios. They're not applicable for general-purpose libraries.\n\n⭐ - known to have a visible impact on performance (typically, in synthetic benchmarks).\n\n> [!NOTE]\n> Numeric `DOTNET_*` values are parsed as **hexadecimal** unless a row says otherwise: `DOTNET_GCHeapCount=16` means 22. Most knobs also have a `runtimeconfig.json`\u002FMSBuild form that takes decimal values (`System.GC.HeapCount`, `System.Threading.ThreadPool.MinThreads`, `\u003CServerGarbageCollector>`, `\u003CTieredPGO>`). All of them are read once at startup.\n\n| Environment variable (CoreCLR Release, .NET 10.0+) | Default | Values to try | Purpose and trade-offs |\n| --- | --- | --- | --- |\n| ⭐`DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS` | `0` | `1` | Run I\u002FO continuations directly on poller threads instead of dispatching them to workers - beneficial for workloads with many small requests. ⚠️ Blocking or lengthy continuations can stall I\u002FO ⚠️ Kestrel still dispatches application code to the thread pool unless its `SocketTransportOptions.UnsafePreferInlineScheduling` is also set, which then runs request handlers on the I\u002FO threads. |\n| ⭐`DOTNET_ThreadPool_UnfairSemaphoreSpinLimit` | `70` in .NET 10.0; `1024` in current .NET 11.0 | `0`, `0x400`, `0xC00` | Trade worker wake latency against CPU spent spinning. `0` might be useful as a mitigation for high CPU usage (especially on arm64) at the cost of throughput. Large values might improve synthetic benchmark performance. |\n| ⭐`DOTNET_GCDynamicAdaptationMode` | `1` | `0` | Disable DATAS for server GC. Can improve throughput at the expense of memory; compare against tuning DATAS (e.g. `DOTNET_GCDGen0GrowthPercent`) rather than disabling it. |\n| ⭐`DOTNET_HillClimbing_Disable` | `0` | `1` | Disable ThreadPool hill climbing (improves time to steady state). |\n| `DOTNET_SYSTEM_NET_SOCKETS_THREAD_COUNT` | Automatic; processor count when inline completions are enabled | `1`, `2`, `4`, `8`, up to processor count | Tune I\u002FO poller count. Fewer pollers can reduce scheduling and cache overhead; too few can serialize processing. Values are decimal. |\n| `DOTNET_gcServer` | varies by environment | `1` | Typically enabled by default for server workloads. Might improve the performance of desktop\u002Fconsole tools. |\n| `DOTNET_GCDTargetTCP` | Effective target `2%` (configuration `0`) | `1` | Lower DATAS's target throughput-cost percentage. Encourages more heaps when GC cost exceeds the target. Only relevant with DATAS enabled. |\n| `DOTNET_GCDGen0GrowthPercent` | Effective multiplier `100%` (configuration `0`) | `0xC8` | Scale DATAS's gen0 budget multiplier to 200% of its default. Trades memory for potentially fewer collections. Only relevant with DATAS enabled. |\n| `DOTNET_GCHeapAffinitizeMask`, `DOTNET_GCNoAffinitize` | GC threads affinitized to all cores | A core mask, or `1` | Keep server GC threads on a subset of cores, or let them float, when the process doesn't own the whole machine. |\n| `DOTNET_MaxVectorTBitWidth` | Automatic (`Vector\u003CT>` is at most 256-bit) | `512` | Allow 512-bit `Vector\u003CT>`. Decimal. |\n| `DOTNET_ThreadPool_ForceMinWorkerThreads` | Processor count | e.g. `0x40` | Minimum worker threads created without delay. A stopgap for [thread pool starvation](#thread-pool-starvation) or bursty blocking work. |\n| `DOTNET_TC_CallCountingDelayMs` | `100` ms; longer on a single processor | `0` | Only for short-lived console tools where JIT warmup takes too long: remove the tiering delay while retaining the normal call-count threshold. Can increase compilation work early in execution. |\n| `DOTNET_TieredCompilation` | `1` | `0` | Alternative for short-lived console tools where JIT warmup takes too long: disable tiering and JIT optimized code on first use. Removes tiered warmup but disables dynamic PGO and can increase initial JIT cost. |\n\nUsing NativeAOT or disabling tiered compilation via `DOTNET_TC_CallCountingDelayMs=0`\u002F`DOTNET_TieredPGO=0`\u002F`DOTNET_TieredCompilation=0` may significantly improve performance for short-lived console tools or benchmarks that measure the entire process rather than just steady-state execution.\n\n# NativeAOT-specific tuning\n\nRecommended reading: Microsoft's [Optimizing AOT deployments](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fdotnet\u002Fcore\u002Fdeploying\u002Fnative-aot\u002Foptimizing) article, plus the runtime's [CPU instruction-set tuning guide](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002F9da219572c1\u002Fsrc\u002Fcoreclr\u002Fnativeaot\u002Fdocs\u002Foptimizing.md). The latter describes implementation-specific options that can change between SDK versions.\n\n> [!NOTE]\n> These are publish-time MSBuild properties: republish for each experiment. NativeAOT has no JIT or tiered compilation, so the CoreCLR JIT\u002Ftiering environment variables above do not apply.\n\n| MSBuild setting | Purpose and trade-offs |\n| --- | --- |\n| `-p:OptimizationPreference=Speed` | Optimize for execution time (`--Ot`) instead of the default size\u002Fspeed balance. A good throughput experiment, but can increase executable size and instruction-cache pressure. Also applies the framework's static PGO data (`IlcPgoOptimize`). |\n| `-p:OptimizationPreference=Size` | Optimize for executable size (`--Os`). Useful when distribution size or memory footprint matters more than peak throughput; measure the speed trade-off. |\n| `-p:IlcInstructionSet=native` | Target instruction sets supported by the build machine's CPU. Useful for local benchmarks or homogeneous deployments; no cross-architecture compilation support, and the executable may not run on older CPUs. Use an explicit instruction-set baseline for heterogeneous deployments. |\n| `-p:IlcInstructionSet=x86-64-v3` | The x64 equivalent: raise the baseline to AVX2-class CPUs (`x86-64-v4` adds AVX-512). The executable refuses to start on older CPUs. |\n| `-p:IlcMaxVectorTBitWidth=512` | Widen `Vector\u003CT>` (needs a matching instruction set). The compile-time counterpart of `DOTNET_MaxVectorTBitWidth`. |\n| `-p:IlcInstructionSet=armv8.1-a` | If `IlcInstructionSet=native` is not acceptable for you, consider explicitly raising the baseline from ARMv8.0-A to ARMv8.1-A to include LSE (crucial for atomics-heavy workloads). |\n\n```sh\ndotnet publish -c Release -r linux-arm64 -p:PublishAot=true -p:OptimizationPreference=Speed -p:IlcInstructionSet=native\n```\n\n# General notes\n\n- Consider filing a [dotnet\u002Fruntime](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fissues) issue only for a clear, reproducible missed optimization in the JIT or libraries with meaningful measured impact. Check for duplicates, include a minimal repro and measurements, and obtain the user's approval before filing.\n- Compare equivalent execution plans before blaming the language. Consider pairwise comparisons, stronger filtering, and verification requirements before tuning code generation.\n- Don't draw conclusions about workloads from tiny synthetic benchmark results.","# Blazing Fast .NET Performance\n\n> [!WARNING]\n> Much of the advice here might not age well: as .NET evolves, old hacks and custom knobs may become obsolete or even detrimental. Apply only after careful consideration and testing. Revisit periodically to ensure continued relevance.\n\nThis skill focuses on optimizing .NET applications for maximum performance, providing tips, techniques, and best practices to achieve blazing-fast execution times. It targets .NET 10.0 and 11.0. Older versions have not been tested.\n\n**Contents**\n\n- [Profiling and Performance Analysis](#profiling-and-performance-analysis)\n  - [dotnet-counters](#dotnet-counters)\n  - [dotnet-trace](#dotnet-trace)\n  - [Low-level profiling with Linux `perf`](#low-level-profiling-with-linux-perf)\n  - [Popular low-level problems](#popular-low-level-problems)\n  - [Inspecting JIT codegen](#inspecting-jit-codegen)\n- [Best practices for high-performance C# code](#best-practices-for-high-performance-c-code)\n  - [Quick tips](#quick-tips)\n  - [Strings and globalization](#strings-and-globalization)\n  - [Thread pool starvation](#thread-pool-starvation)\n  - [Contention on shared state](#contention-on-shared-state)\n    - [False sharing](#false-sharing)\n  - [Safe vectorization (SIMD or SWAR)](#safe-vectorization-simd-or-swar)\n  - [Generic specialization via static abstract members](#generic-specialization-via-static-abstract-members)\n- [ASP.NET Core](#aspnet-core)\n  - [Hosting and logging](#hosting-and-logging)\n  - [JSON and response writing](#json-and-response-writing)\n  - [Routing and middleware](#routing-and-middleware)\n  - [Kestrel socket transport](#kestrel-socket-transport)\n  - [Beyond benchmarks](#beyond-benchmarks)\n- [Magic knobs to Tune .NET Performance](#magic-knobs-to-tune-net-performance)\n- [NativeAOT-specific tuning](#nativeaot-specific-tuning)\n- [General notes](#general-notes)\n\n# Profiling and Performance Analysis\n\nStart cheap and go deeper only when needed: counters (what kind of bottleneck?) → traces (where?) → `perf` and JIT disassembly.\nInstall the tools with:\n\n```sh\ndotnet tool install -g dotnet-counters\ndotnet tool install -g dotnet-trace\ndotnet tool install -g dotnet-stack\ndotnet tool install -g dotnet-symbol\n```\n\n## dotnet-counters\n\n[dotnet-counters](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fdiagnostics\u002Fdotnet-counters) is a low-overhead, first-level triage tool. Run it while the app is under load:\n\n```sh\ndotnet-counters monitor -p \u003Cpid>                                    # System.Runtime counters by default\ndotnet-counters monitor -p \u003Cpid> --counters System.Runtime,Microsoft.AspNetCore.Hosting,System.Net.Http\ndotnet-counters collect -p \u003Cpid> --format csv -o counters.csv      # save for post-processing\n```\n\nSee the `System.Runtime` [built-in metrics](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fdiagnostics\u002Fbuilt-in-metrics-runtime) (.NET 9+) for what each counter means. Typical readings:\n\n| Symptom | Counters | Next step |\n| --- | --- | --- |\n| Low CPU, rising latency | `dotnet.thread_pool.queue.length` grows, `dotnet.thread_pool.thread.count` keeps creeping up | [Thread pool starvation](#thread-pool-starvation) |\n| Throughput doesn't scale with cores | `dotnet.monitor.lock_contentions` rate | `dotnet-trace --clrevents contention`, [Contention on shared state](#contention-on-shared-state) |\n| High CPU, much of it in GC | `dotnet.gc.pause.time`, `dotnet.gc.collections` (gen2 share), `dotnet.gc.heap.total_allocated` rate | [Allocations and GC](#allocations-and-gc) |\n| Unexplained slow requests | `dotnet.exceptions` rate | `dotnet-trace --clrevents exception` |\n| Numbers still drifting | `dotnet.jit.compiled_methods` still growing | Not at steady state yet: warm up longer |\n\n## dotnet-trace\n\n[dotnet-trace](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fdiagnostics\u002Fdotnet-trace) collects EventPipe traces on any OS without root or a native profiler (managed frames only):\n\n```sh\ndotnet-trace collect -p \u003Cpid> --duration 00:00:00:10    # default profiles: dotnet-common + dotnet-sampled-thread-time\ndotnet-trace collect -p \u003Cpid> --profile gc-verbose      # GCs + sampled allocations: which types\u002Fstacks allocate\ndotnet-trace collect -p \u003Cpid> --clrevents contention    # lock contention events with stacks\ndotnet-trace collect -p \u003Cpid> --providers Microsoft-Windows-DotNETRuntime:0x80000000000:4  # .NET 10+: randomized allocation sampling, cheaper than gc-verbose\ndotnet-trace collect -- .\u002FMyApp arg1                    # trace from startup\ndotnet-trace list-profiles\n\ndotnet-trace report trace.nettrace topN -n 20           # top methods (exclusive) as plain text\ndotnet-trace report trace.nettrace topN -n 20 --inclusive\ndotnet-trace convert trace.nettrace --format Speedscope # open in https:\u002F\u002Fwww.speedscope.app\n```\n\nOn Linux with kernel 6.4+ and root, `dotnet-trace collect-linux` (.NET 10+) records native, kernel and managed frames in one trace without the `perf` setup below.\n\nFor more tips on using `dotnet-trace`, refer to the [dotnet-trace collection skill](https:\u002F\u002Fraw.githubusercontent.com\u002Fdotnet\u002Fskills\u002Frefs\u002Fheads\u002Fmain\u002Fplugins\u002Fdotnet-diag\u002Fskills\u002Fdotnet-trace-collect\u002FSKILL.md).\n\n## Low-level profiling with Linux `perf`\n\nThe instructions below are mostly for CoreCLR\u002FJIT scenarios on Linux with the native `perf` tool. NativeAOT\u002FR2R scenarios should presumably work as is, without `perf inject`.\n\nTypically, the following environment variables need to be set in the context of the running application:\n\n```sh\nDOTNET_PerfMapEnabled=1\nDOTNET_PerfMapStubGranularity=3\nDOTNET_JitFramed=1\nDOTNET_EnableWriteXorExecute=0\n```\n\n`DOTNET_PerfMapEnabled` has an overhead of its own: set it for the profiling run only, not for the runs you take throughput numbers from.\n\nWhat to profile depends on the scenario. Typically it's steady-state performance, so wait until the process is properly warmed up (e.g. 1 minute), then attach and collect traces:\n\n```sh\nperf record -k 1 -g -F 3999 -p $PROCESS_PID -o perf.data -- sleep 5\n```\nNext, symbolize the collected JIT frames using `perf inject`:\n\n```sh\nperf inject --input perf.data --jit --output perfjit.data\n```\n\nThen use the resulting `perfjit.data` file as needed, e.g. to generate flame graphs or annotated asm:\n\n```sh\n# annotated asm & report:\nperf report --input perfjit.data --no-children --percent-limit 1 --stdio > perf_report.txt\nperf annotate --stdio2 -i perfjit.data --percent-limit 1 -M intel > perf.asm\n\n# Generate flamegraph (svg)\ngit clone --depth 1 --quiet https:\u002F\u002Fgithub.com\u002Fbrendangregg\u002FFlameGraph || true\nperf script -i perfjit.data | .\u002FFlameGraph\u002Fstackcollapse-perf.pl | .\u002FFlameGraph\u002Fflamegraph.pl > flamegraph.svg\n```\n\nNow `perf.asm` contains the annotated assembly output for the hottest methods in your application. Keep in mind that sample attribution is imprecise (\"skid\"): the cost is often reported on an instruction shortly after the one that actually stalls (e.g. right after a slow load or division), so inspect the neighboring instructions too.\n\nIn case of missing symbols you may need to use `dotnet-symbol` to download the necessary debug symbols for .dll\u002F.dylib\u002F.so:\n\n```sh\ndotnet-symbol --symbols $PUBLISH_FOLDER\u002F*.dll\ndotnet-symbol --symbols $PUBLISH_FOLDER\u002F*.so\n```\n\nAll other `perf`-related techniques (e.g. c2c, branch profiling, cache misses) can be applied similarly, depending on the performance aspects you want to analyze.\n\nFor simple functions, create a self-contained console application that calls the function in an infinite loop. For BenchmarkDotNet, use the in-process toolchain (`-i` or `[InProcess]`) with a long iteration duration and a very large iteration count so it is clear which process to attach `perf` to.\n\n## Popular low-level problems\n\n- Cache contention and false sharing in multi-threaded code. Fix with per-thread data and padding (see [False sharing](#false-sharing)).\n- Bounds checks not being elided by the JIT.\n- Store-to-load forwarding stalls, e.g. writing a struct field by field and reading it back with a wider load.\n- Bad data locality causing cache misses.\n- Code alignment noise. Loop or branch placement, Intel JCC erratum - not something we can control, just to keep in mind when analyzing performance variations.\n- GC's alignment is 8 bytes (64-bit), which might not be suitable for SIMD optimizations (e.g. AVX512 might penalize every load\u002Fstore if misaligned since the cache line is 64 bytes).\n\n## Inspecting JIT codegen\n\nThese are diagnostic switches (not tuning knobs) and work with the shipped Release runtime:\n\n```sh\nDOTNET_JitDisasm=\"Test\"            # method list: 'Test', 'Program:Test', '*Test*', 'Program:*' (space-separated)\nDOTNET_JitDisasmSummary=1          # list every jitted method with its tier\nDOTNET_JitStdOutFile=\u002Ftmp\u002Fjit.txt  # write JIT output to a file instead of stdout\nDOTNET_JitDisasmDiffable=1         # stable output (no addresses) for before\u002Fafter diffs\nDOTNET_JitDisasmOnlyOptimized=1    # show only Tier-1\u002FFullOpts code\n```\n^ might require `MethodImplOptions.NoInlining` on the method to ensure it gets JIT-compiled and not inlined away.\n\n> [!TIP]\n> Set them only for the app process, not for `dotnet run`, which would also dump the SDK's own methods.\n\n- A method is compiled several times as it tiers up. Judge the last version: header `; Tier1 code` (or `; Tier1-OSR code` \u002F `; FullOpts code`), ideally with `; optimized using Dynamic PGO`. `; Tier0 code` and `; Instrumented Tier0 code` are unoptimized.\n- BCL methods precompiled with ReadyToRun show up only once they get hot and are re-jitted at Tier1.\n- NativeAOT: pass the same options to ILC, e.g. `\u003CIlcArg Include=\"--codegenopt:JitDisasm=Test\" \u002F>` and `\u003CIlcArg Include=\"--codegenopt:JitStdOutFile=jit.txt\" \u002F>`.\n- Inlining decisions: `DOTNET_JitPrintInlinedMethods` needs a Checked JIT. On the Release runtime, collect the `MethodJitInliningSucceeded`\u002F`MethodJitInliningFailed` events (with the reason) via `dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:0x1000:5`.\n\n# Best practices for high-performance C# code\n\n> [!IMPORTANT]\n> Only safe C# code is considered in these best practices. Unsafe code is highly discouraged and not covered here.\n\n## Quick tips\n\n- Use [source-generated System.Text.Json](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fserialization\u002Fsystem-text-json\u002Fsource-generation): pass generated `JsonTypeInfo\u003CT>` to avoid reflection-based metadata discovery and enable supported serialization fast paths. Preserve existing serialization options and behavior.\n- Use [`[LoggerMessage]`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fextensions\u002Flogging\u002Fsource-generation) source-generated logging to avoid template parsing, boxing, and temporary allocations. Still guard expensive argument computation with `IsEnabled`.\n- Use [`[GeneratedRegex]`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fbase-types\u002Fregular-expression-source-generators) for compile-time-known patterns instead of runtime pattern compilation. Preserve options, culture, and match timeouts.\n- Prefer optimized bulk APIs over handwritten loops: `Span.CopyTo`, `Clear`, `Fill`, `SequenceEqual`, and `IndexOf` already have specialized implementations, including SIMD where appropriate.\n- Use RVA-backed spans `static ReadOnlySpan\u003Clong> RVA => [1, 2, 3];` for immutable data (only primitives, e.g., `long`, `byte`, `char`) - the data will be embedded into metadata, and the JIT will be able to perform constant folding on it.\n- Prefer static lambdas and state-passing overloads in hot paths to avoid capturing closures.\n- Parse and format UTF-8 directly: numeric `TryParse(ReadOnlySpan\u003Cbyte>, ...)` and `TryFormat(Span\u003Cbyte>, ...)` avoid intermediate strings and UTF-8\u002FUTF-16 transcoding. Preserve culture and formatting semantics.\n- Cache `SearchValues\u003Cchar>` \u002F `SearchValues\u003Cbyte>` for repeated searches against fixed sets; use `SearchValues\u003Cstring>` for multi-substring searches. Construction has a cost, so do not create these per call.\n- Consider `FrozenDictionary` \u002F `FrozenSet` for build-once, read-many data. Their construction cost must be amortized over enough lookups; preserve the intended comparer.\n- Use dictionary alternate lookups from spans: `dictionary.GetAlternateLookup\u003CReadOnlySpan\u003Cchar>>()` avoids allocating temporary string keys when the comparer supports it. Use `TryGetAlternateLookup` when comparer support is not known.\n- Implement `IEquatable\u003CT>` and a good `GetHashCode` for struct dictionary\u002Fset keys. This can avoid expensive default equality paths, boxing, and collision-heavy hashes.\n- Try [`TensorPrimitives`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fapi\u002Fsystem.numerics.tensors.tensorprimitives) before hand-vectorizing numeric operations. Its span-based implementations handle SIMD and tails; prefer it over manual SIMD loops or LINQ for things like finding min\u002Fmax, sums, etc.\n- Prefer [`[LibraryImport]`](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fnative-interop\u002Fpinvoke-source-generation) over `[DllImport]` for supported signatures to generate marshalling at build time; both `DllImport` and `LibraryImport` require `AllowUnsafeBlocks=true` under unsafe-v2, but if interop is not avoidable, `LibraryImport` is generally preferred.\n- Use `static readonly` flags for genuinely immutable process-wide configuration. Optimized JIT code can fold their values and eliminate disabled branches.\n- Cache fields in locals in hot loops (`var items = _items;`). The JIT often can't do it for you: calls and stores through references may modify the field, and the memory model restricts hoisting reads. Locals also enable bounds check elimination for arrays\u002Fspans.\n- Consider `Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping` for UTF-8 JSON-only responses ([documentation](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fstandard\u002Fserialization\u002Fsystem-text-json\u002Fcharacter-encoding#serialize-all-characters)). Never embed its raw output in HTML or `\u003Cscript>`; any custom encoder disables STJ's generated serialization fast path, so benchmark rather than assuming the gains add up.\n- Create `JsonSerializerOptions` once and reuse it (CA1869). A new instance with the same settings still pays a global cache lookup; one that also creates converter instances (`Converters = { new JsonStringEnumConverter() }`) misses that cache, since converters compare by reference, and rebuilds metadata for every type it touches on each call.\n- For most bit\u002Fbyte manipulations, prefer the built-in `BitOperations`, `BinaryPrimitives`, `BitConverter` or static numeric helpers such as `uint.Log2`, `uint.PopCount`, `uint.TrailingZeroCount`, etc.\n- Span\u002FReadOnlySpan have a lot of useful extension methods via `MemoryExtensions` such as `SequenceEqual`, `IndexOf`, `IndexOfAnyExcept`, `LastIndexOf`, `StartsWith`, `CommonPrefixLength`, and a lot more - prefer these APIs over manual loops or vectorization.\n- Don't pin benchmarks to a single core - doing so might significantly affect internal VM heuristics (e.g. tiered compilation timing).\n- For BenchmarkDotNet methodology, see the [dotnet\u002Fruntime microbenchmark skill](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fmicrobenchmark\u002FSKILL.md).\n- `AggressiveOptimization` is usually a deoptimization.\n- Overusing `AggressiveInlining` may hurt performance (e.g. by exhausting the inliner's time budget or increasing the number of tracked local variables).\n- Moving rarely taken paths (grow\u002Fresize, slow fallback) into a separate method marked with  `[MethodImpl(MethodImplOptions.NoInlining)]` may improve performance (as `List\u003CT>.Add` does with `AddWithResize`), but it may also hinder escape analysis.\n- Don't put too much data into TLS (Thread-Local Storage) - excessive use can lead to increased memory usage and cache pressure, reducing overall performance (there can be a lot of threads, each with its own copy).\n- Consider `CollectionsMarshal.AsSpan(list)` for `List\u003CT>` (there is no `List\u003CT>.AsSpan()`), or `.AsSpan()` on collections that support it, to avoid unnecessary overhead from bound and covariance checks, and to enable span-based algorithms directly on the collection's underlying memory. Don't add or remove list items while the span is in use.\n- Prefer XXH3 via Microsoft's `System.IO.Hashing.XxHash3` for fast fingerprinting of large blobs. Use `XxHash128` when 128-bit fingerprints are needed. For exact deduplication, verify matches byte for byte. Use cryptographic hashes when security requires them. NOTE: Be careful with data that may contain garbage or uninitialized bytes (e.g. arrays of structs with padding).\n- Split without allocating: `foreach (Range r in span.Split(','))` (.NET 9+) or `span.Split(destinationRanges, ',')`, instead of `string.Split` plus `Substring`.\n- Use `System.Threading.Lock` (.NET 9+) rather than `object` for new `lock` targets.\n- For tiny loop bodies, `Parallel.For` pays a delegate call per element. Iterate ranges instead: `Parallel.ForEach(Partitioner.Create(0, n), range => { for (int i = range.Item1; i \u003C range.Item2; i++) ... })`.\n- `Console.Out` flushes after every write. For heavy output, write through one buffered `Stream`\u002F`StreamWriter` over `Console.OpenStandardOutput()` and flush once at the end.\n- To read a large file from several threads, use `File.OpenHandle` + `RandomAccess.Read(handle, buffer, fileOffset)`: offset-based, thread-safe, no shared position and no `FileStream` buffering layer.\n\n## Strings and globalization\n\n- Prefer ordinal operations. Culture-sensitive comparisons go through ICU, are many times slower than ordinal ones, and are often a correctness bug anyway.\n- Culture-sensitive by default: `string.Compare`, `CompareTo`, `StartsWith(string)`, `EndsWith(string)`, `IndexOf(string)`, `LastIndexOf(string)`, `ToUpper()`, `ToLower()`, and default string sorting (`Array.Sort`, `List\u003CT>.Sort`, `OrderBy`, `SortedDictionary`\u002F`SortedSet` with the default comparer). Pass `StringComparison.Ordinal`\u002F`OrdinalIgnoreCase` or `StringComparer.Ordinal`; analyzers CA1307, CA1309 and CA1310 find these calls.\n- Already ordinal: `==`, `Equals`, `Contains(string)`, `Replace(string, string)`, `IndexOf(char)`, `Split`, `GetHashCode`, and the default `Dictionary`\u002F`HashSet` comparer.\n- Case-insensitive equality: `string.Equals(a, b, StringComparison.OrdinalIgnoreCase)` instead of `a.ToLower() == b.ToLower()` (two allocations plus culture rules). For ASCII data, `System.Text.Ascii` has vectorized `EqualsIgnoreCase`, `ToUpper`\u002F`ToLower` (span to span) and `IsValid`.\n- `Dictionary\u003Cstring, T>` \u002F `HashSet\u003Cstring>` use fast non-randomized hashing only with the default comparer, `StringComparer.Ordinal` or `StringComparer.OrdinalIgnoreCase` (falling back to randomized hashing only on heavy collisions). Any other comparer loses it.\n- Stay in UTF-8 when input and output are UTF-8: search, parse and format `ReadOnlySpan\u003Cbyte>` directly (`u8` literals, `SearchValues\u003Cbyte>`, `TryParse`\u002F`TryFormat` on bytes, `Utf8.TryWrite`) and convert to `string` only when needed.\n- If the app needs no culture data, `\u003CInvariantGlobalization>true\u003C\u002FInvariantGlobalization>` removes the ICU dependency (faster startup, less memory) and makes culture-sensitive string operations ordinal. This changes behavior, so verify the output.\n\n## Thread pool starvation\n\nSymptoms: low CPU and rising latency while `dotnet.thread_pool.queue.length` grows and `dotnet.thread_pool.thread.count` creeps up (beyond the minimum, the pool adds threads slowly). The cause is pool threads blocked instead of running work: sync-over-async (`.Result`, `.Wait()`, `GetAwaiter().GetResult()`), synchronous I\u002FO, or locks held across slow operations.\n\n```sh\ndotnet-stack report -p \u003Cpid>   # all managed stacks: many threads parked in the same Wait\u002FGetResult frame is the culprit\ndotnet-trace collect -p \u003Cpid> --providers Microsoft-Windows-DotNETRuntime:0x40000000000:5  # .NET 9+: wait events with stacks\n```\n\nThe fix is to make the blocked path async end to end; wrapping it in `Task.Run` only moves the blocked thread. As a stopgap, raise the minimum worker count with `ThreadPool.SetMinThreads` or `DOTNET_ThreadPool_ForceMinWorkerThreads` (see the knobs table).\n\n## Contention on shared state\n\nWrites to one shared location from many threads (`Interlocked.Increment` on a global counter, a shared \"total\" field, etc.) serialize all of them on a single cache line, so throughput can get *worse* as cores are added. Split the state, update only the local part, and combine on read. Two options:\n\n**1. Per-core stripes** - index an array by `Thread.GetCurrentProcessorId()` (cheap: cached and periodically refreshed). The id is only a hint: threads migrate, and ids can exceed `ProcessorCount` (hence the modulo), so keep `Interlocked` on the stripe - it's almost always uncontended. Fixed memory and allocation-free reads; works with any number of threads (thread pool, request handlers). Stripes must be padded, see [False sharing](#false-sharing).\n\n```cs\nsealed class StripedCounter\n{\n    private readonly PaddedLong[] _stripes = new PaddedLong[Environment.ProcessorCount];\n\n    public void Increment()\n    {\n        PaddedLong[] stripes = _stripes;\n        Interlocked.Increment(ref stripes[(uint)Thread.GetCurrentProcessorId() % (uint)stripes.Length].Value);\n    }\n\n    \u002F\u002F Not an atomic snapshot, which is fine for statistics.\n    public long Sum()\n    {\n        PaddedLong[] stripes = _stripes;\n        long sum = 0;\n        for (int i = 0; i \u003C stripes.Length; i++)\n            sum += Volatile.Read(ref stripes[i].Value);\n        return sum;\n    }\n}\n```\n\n**2. `ThreadLocal\u003CT>` with `trackAllValues: true`** - each thread updates its own slot with no atomics at all; sum `Values` on read. `Values` allocates a list and includes values of threads that have already exited, and every thread that ever touched it keeps a slot, so this suits a bounded set of long-lived threads (e.g. `Parallel` workers) where writes vastly outnumber reads. Dispose the `ThreadLocal\u003CT>` when done.\n\n```cs\nprivate readonly ThreadLocal\u003Clong> _count = new(trackAllValues: true);\n\nvoid OnItem() => _count.Value++;\nlong Total() => _count.Values.Sum();\n```\n\nIf the work structure allows it, aggregating into plain locals and combining once per task\u002Fpartition (`Parallel.For` with `localInit`\u002F`localFinally`) is cheaper than both.\n\n### False sharing\n\nThreads writing *different* variables still contend if those variables share a cache line (64 bytes on most x64\u002Farm64 CPUs, 128 bytes on Apple M-series): every write invalidates the line in the other cores' caches. The naive striped counter is the classic case:\n\n```cs\n\u002F\u002F 8 longs per 64-byte line: the per-core slots look independent, but cores keep stealing the line from each other.\nlong[] stripes = new long[Environment.ProcessorCount];\nInterlocked.Increment(ref stripes[(uint)Thread.GetCurrentProcessorId() % (uint)stripes.Length]);\n```\n\nFix it by padding each slot so no two hot values can land on the same line:\n\n```cs\nstruct PaddedLong\n{\n    public long Value;\n#pragma warning disable CS0169 \u002F\u002F padding only\n    private InlineArray8\u003Clong> _padding; \u002F\u002F 64 bytes: adjacent Values are 72 bytes apart\n#pragma warning restore CS0169\n}\n```\n\n- Most server hardware (all x64 CPUs and Neoverse-based arm64 CPUs such as Cobalt and Graviton) has 64-byte cache lines, so 64 bytes of padding is usually sufficient. Apple M-series CPUs have 128-byte cache lines, so consider padding to 128 bytes there.\n- The same applies to fields of one object written by different threads (e.g. a ring buffer's head and tail indices): put a padding field between them.\n\n## Safe vectorization (SIMD or SWAR)\n\nIn .NET 11.0 the JIT got improvements in bounds check elimination and now recognizes the following shape for safely loading and processing multiple elements from an array\u002Fspan:\n\n```cs\nwhile (data.Length >= CNS)\n{\n    \u002F\u002F JIT knows that the slice has at least CNS elements, so bounds checks can be elided.\n    \u002F\u002F e.g.:\n    \u002F\u002F  VectorX.Create(data);\n    \u002F\u002F  BitConverter.ToInt32(data);\n    \u002F\u002F  BinaryPrimitives.ReadInt32LittleEndian(data);\n    \u002F\u002F  etc.\n    data = data.Slice(CNS);\n}\n```\n\nFor example, the following C# snippet has no bounds checks in .NET 11.0:\n\n```cs\nint Sum(Span\u003Cint> data)\n{\n    Vector128\u003Cint> sum = default;\n    while (data.Length >= Vector128\u003Cint>.Count)\n    {\n        sum += Vector128.Create(data);\n        data = data.Slice(Vector128\u003Cint>.Count);\n    }\n    int result = Vector128.Sum(sum);\n    foreach (var t in data)\n        result += t;\n    return result;\n}\n```\n\n.NET 12.0 (Preview 1 or main) extends it to more complex loop shapes (e.g. multiple spans):\n\n```cs\nstatic bool Equal(ReadOnlySpan\u003Cushort> a, ReadOnlySpan\u003Cushort> b)\n{\n    if (a.Length != b.Length)\n        return false;\n\n    int i = 0;\n    if (Vector128.IsHardwareAccelerated)\n    {\n        for (; i \u003C= a.Length - Vector128\u003Cushort>.Count; i += Vector128\u003Cushort>.Count)\n        {\n            var va = Vector128.Create(a.Slice(i));\n            var vb = Vector128.Create(b.Slice(i));\n            if (va != vb)\n                return false;\n        }\n    }\n\n    for (; i \u003C a.Length; i++)\n    {\n        if (a[i] != b[i])\n            return false;\n    }\n\n    return true;\n}\n```\n^ has no bounds checks for the main loop in .NET 12.0 (Preview 1).\n\n> [!TIP]\n> This only illustrates the loop shape. For this exact task, just call `a.SequenceEqual(b)`.\n\nMore info about vectorization and SIMD in .NET can be found in the [official documentation](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fdotnet\u002Fstandard\u002Fsimd). Also consider using [TensorPrimitives](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fapi\u002Fsystem.numerics.tensors.tensorprimitives). The [dotnet\u002Fruntime vectorization skill](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fvectorization\u002FSKILL.md) has authoring and testing checklists (remainder handling, running with `DOTNET_EnableAVX2=0` \u002F `DOTNET_EnableHWIntrinsic=0` to cover every path); the `unsafe` load\u002Fstore variants it mentions are out of scope here.\n\n## Generic specialization via static abstract members\n\nStatic abstract interface members let you specialize a generic method for values supplied by struct tags, without duplicating its source code. The JIT generates separate native versions of `Big\u003CC42>` and `Big\u003CC100>`. In optimized code, it can inline the small `Value` getters and fold expressions and branches that use `T.Value`. This works even when `Big\u003CT>` itself is too large to inline; The tradeoff is increased native code size. `T.Value` is not a C# compile-time constant: constant folding depends on JIT optimizations and getter inlining.\n\n```cs\nusing System.Runtime.CompilerServices;\n\ninterface IConstant {\n    static abstract int Value { get; }\n}\n\nreadonly struct C42 : IConstant {\n    public static int Value => 42;\n}\n\nreadonly struct C100 : IConstant {\n    public static int Value => 100;\n}\n\nstatic class Example\n{\n    static int Big\u003CT>(int x) where T : struct, IConstant\n    {\n        int arg = T.Value;\n\n        \u002F\u002F The large body would go here.\n\n        return arg == 42 ? x * arg : x + arg;\n    }\n\n    static int Use42(int x) => Big\u003CC42>(x);\n    static int Use100(int x) => Big\u003CC100>(x);\n}\n```\n\nIt is also handy for scenarios where you want to leverage different vector sizes or constants in a generic way, allowing the JIT to generate optimized code for each specialization without duplicating the method's source code. Example: summing an array using different vector widths with static abstract members.\n\n```cs\nusing System;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.Intrinsics;\n\nclass Program\n{\n    static void Main()\n    {\n        int[] values = new int[1003];\n\n        if (Vector128.IsHardwareAccelerated)\n            Console.WriteLine(Sum\u003CVector128\u003Cint>, Ops128>(values));\n        if (Vector256.IsHardwareAccelerated)\n            Console.WriteLine(Sum\u003CVector256\u003Cint>, Ops256>(values));\n        if (Vector512.IsHardwareAccelerated) \n            Console.WriteLine(Sum\u003CVector512\u003Cint>, Ops512>(values));\n    }\n\n    [MethodImpl(MethodImplOptions.NoInlining)]\n    static int Sum\u003CV, TOps>(ReadOnlySpan\u003Cint> s)\n        where V : struct\n        where TOps : struct, IOps\u003CV>\n    {\n        V acc = default;\n        int count = TOps.Count;\n\n        while (s.Length >= count)\n        {\n            acc = TOps.Add(acc, TOps.Load(s));\n            s = s.Slice(count);\n        }\n\n        int sum = TOps.Sum(acc);\n        foreach (int x in s) sum = unchecked(sum + x);\n        return sum;\n    }\n}\n\n\ninterface IOps\u003CV> where V : struct\n{\n    static abstract int Count { get; }\n    static abstract V Load(ReadOnlySpan\u003Cint> s);\n    static abstract V Add(V a, V b);\n    static abstract int Sum(V v);\n}\n\nreadonly struct Ops128 : IOps\u003CVector128\u003Cint>>\n{\n    public static int Count => Vector128\u003Cint>.Count;\n    public static Vector128\u003Cint> Load(ReadOnlySpan\u003Cint> s) => Vector128.Create(s);\n    public static Vector128\u003Cint> Add(Vector128\u003Cint> a, Vector128\u003Cint> b) => a + b;\n    public static int Sum(Vector128\u003Cint> v) => Vector128.Sum(v);\n}\n\nreadonly struct Ops256 : IOps\u003CVector256\u003Cint>>\n{\n    public static int Count => Vector256\u003Cint>.Count;\n    public static Vector256\u003Cint> Load(ReadOnlySpan\u003Cint> s) => Vector256.Create(s);\n    public static Vector256\u003Cint> Add(Vector256\u003Cint> a, Vector256\u003Cint> b) => a + b;\n    public static int Sum(Vector256\u003Cint> v) => Vector256.Sum(v);\n}\n\nreadonly struct Ops512 : IOps\u003CVector512\u003Cint>>\n{\n    public static int Count => Vector512\u003Cint>.Count;\n    public static Vector512\u003Cint> Load(ReadOnlySpan\u003Cint> s) => Vector512.Create(s);\n    public static Vector512\u003Cint> Add(Vector512\u003Cint> a, Vector512\u003Cint> b) => a + b;\n    public static int Sum(Vector512\u003Cint> v) => Vector512.Sum(v);\n}\n```\n\n# ASP.NET Core\n\nCode-level advice for HTTP throughput benchmarks. To run the TechEmpower apps against a locally built runtime, see the [dotnet\u002Fruntime aspnet-benchmark skill](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Faspnet-benchmark\u002FSKILL.md).\n\n## Hosting and logging\n\n- Start from `WebApplication.CreateSlimBuilder(args)` (or `CreateEmptyBuilder` + `builder.WebHost.UseKestrelCore()`) and add only what's needed. Every middleware component (HTTPS redirection, auth, CORS, response compression, static files, developer exception page) costs something per request. The slim builder drops HTTPS configuration, HTTP\u002F3, regex route constraints, IIS integration and hosting startup assemblies, but still reads `appsettings.json` and still adds a console logger.\n- Logging: without an `appsettings.json` that raises `Microsoft.AspNetCore` to `Warning`, the default level is `Information` and the host logs every request to the console - a massive bottleneck. Raising the level removes the log writes, but while any provider is registered the host still creates an `Activity` and a logging scope per request; `builder.Logging.ClearProviders()` removes those too.\n- Measure in the `Production` environment. `Development` turns on DI `ValidateScopes`\u002F`ValidateOnBuild`, and `dotnet run` picks it up from `launchSettings.json`: run the published output, or `dotnet run -c Release --no-launch-profile`.\n\n## JSON and response writing\n\n- JSON: register source-generated metadata with `builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));`. For hand-written responses, serialize with a reused `Utf8JsonWriter` (`writer.Reset(response.BodyWriter)`) instead of allocating one per request.\n- Make response DTOs `sealed` (or structs). A minimal API handler returning a sealed `T` gets a direct serialization path chosen once when the endpoint is built; for a non-sealed class the framework calls `GetType()` on every response and falls back to serializing as `object` when the runtime type differs.\n- On the hottest endpoints return `T` itself. `TypedResults.Ok(value)` and `Results.Json(value)` do a type-info lookup and a `GetType()` check per call even for sealed types.\n- `WriteAsJsonAsync` never sets `Content-Length`, and Kestrel only fills it in for empty bodies, so HTTP\u002F1.1 responses go out chunked. When the exact size is known up front, set `Response.ContentLength` (a mismatch is an error).\n- Static payloads: keep a pre-encoded `static readonly byte[]`, set `ContentLength`, write once. `Results.Text(utf8Span, ...)` copies the span into a new array on each call; the result object is immutable, so create it once in a static field. Don't pass `statusCode` to it on hot paths: that resolves `ILoggerFactory` and creates a logger per request.\n- Set headers through the typed properties (`response.Headers.ContentType = ...`, analyzer ASP0015): on Kestrel they are direct field writes, while the string indexer goes through a name lookup.\n\n## Routing and middleware\n\n- A handler that is a plain `RequestDelegate` (`app.MapGet(\"\u002Fx\", (HttpContext ctx) => ...)` returning `Task`) bypasses parameter binding and the generated request delegate entirely. If such a lambda returns `Task\u003CT>`, the value is silently discarded (analyzer ASP0016).\n- Inline middleware: use `app.Use(async (context, next) => { ...; await next(context); })`. The `next()` form (`Func\u003CTask>`) allocates a closure per request.\n- Prefer convention-based middleware classes (constructed once) over `IMiddleware`, which is resolved from DI and released on every request.\n- `.ShortCircuit()` on an endpoint (or `app.MapShortCircuit(404, \"favicon.ico\")`) runs it inside the routing middleware and skips the rest of the pipeline. It throws if the endpoint has auth, CORS or antiforgery metadata.\n- Avoid `IHttpContextAccessor` in hot services: once registered (ASP.NET Core Identity does it for you), an `AsyncLocal` is set on every request. Pass `HttpContext` explicitly.\n\n## Kestrel socket transport\n\nConfigure with `builder.WebHost.UseSockets(o => ...)`:\n\n- `WaitForDataBeforeAllocatingBuffer = false` trades memory per idle connection for throughput.\n- `IOQueueCount` sets the number of I\u002FO queues that socket continuations are scheduled on (default is based on the processor count); `0` schedules them directly on the thread pool.\n\n## Beyond benchmarks\n\n- Output caching (`AddOutputCache`, `.CacheOutput()`): the default policy caches only unauthenticated `GET`\u002F`HEAD` 200 responses without `Set-Cookie`.\n- Serve static files with `MapStaticAssets` rather than `UseStaticFiles`: precompressed variants and ETags are computed at build time.\n- Response compression is CPU per request and is off for HTTPS by default; prefer precompressed assets or the reverse proxy.\n- `-p:EnableRequestDelegateGenerator=true` moves minimal API handler codegen from startup to build time (startup win; on by default with `PublishAot` or `PublishTrimmed`).\n\n# Magic knobs to Tune .NET Performance\n\n> [!WARNING]\n> It is highly recommended to keep the default settings unless you have measured and understood the performance implications of changing them. \n\nMost of these knobs are relevant only for server workloads or performance-critical scenarios. They're not applicable for general-purpose libraries.\n\n⭐ - known to have a visible impact on performance (typically, in synthetic benchmarks).\n\n> [!NOTE]\n> Numeric `DOTNET_*` values are parsed as **hexadecimal** unless a row says otherwise: `DOTNET_GCHeapCount=16` means 22. Most knobs also have a `runtimeconfig.json`\u002FMSBuild form that takes decimal values (`System.GC.HeapCount`, `System.Threading.ThreadPool.MinThreads`, `\u003CServerGarbageCollector>`, `\u003CTieredPGO>`). All of them are read once at startup.\n\n| Environment variable (CoreCLR Release, .NET 10.0+) | Default | Values to try | Purpose and trade-offs |\n| --- | --- | --- | --- |\n| ⭐`DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS` | `0` | `1` | Run I\u002FO continuations directly on poller threads instead of dispatching them to workers - beneficial for workloads with many small requests. ⚠️ Blocking or lengthy continuations can stall I\u002FO ⚠️ Kestrel still dispatches application code to the thread pool unless its `SocketTransportOptions.UnsafePreferInlineScheduling` is also set, which then runs request handlers on the I\u002FO threads. |\n| ⭐`DOTNET_ThreadPool_UnfairSemaphoreSpinLimit` | `70` in .NET 10.0; `1024` in current .NET 11.0 | `0`, `0x400`, `0xC00` | Trade worker wake latency against CPU spent spinning. `0` might be useful as a mitigation for high CPU usage (especially on arm64) at the cost of throughput. Large values might improve synthetic benchmark performance. |\n| ⭐`DOTNET_GCDynamicAdaptationMode` | `1` | `0` | Disable DATAS for server GC. Can improve throughput at the expense of memory; compare against tuning DATAS (e.g. `DOTNET_GCDGen0GrowthPercent`) rather than disabling it. |\n| ⭐`DOTNET_HillClimbing_Disable` | `0` | `1` | Disable ThreadPool hill climbing (improves time to steady state). |\n| `DOTNET_SYSTEM_NET_SOCKETS_THREAD_COUNT` | Automatic; processor count when inline completions are enabled | `1`, `2`, `4`, `8`, up to processor count | Tune I\u002FO poller count. Fewer pollers can reduce scheduling and cache overhead; too few can serialize processing. Values are decimal. |\n| `DOTNET_gcServer` | varies by environment | `1` | Typically enabled by default for server workloads. Might improve the performance of desktop\u002Fconsole tools. |\n| `DOTNET_GCDTargetTCP` | Effective target `2%` (configuration `0`) | `1` | Lower DATAS's target throughput-cost percentage. Encourages more heaps when GC cost exceeds the target. Only relevant with DATAS enabled. |\n| `DOTNET_GCDGen0GrowthPercent` | Effective multiplier `100%` (configuration `0`) | `0xC8` | Scale DATAS's gen0 budget multiplier to 200% of its default. Trades memory for potentially fewer collections. Only relevant with DATAS enabled. |\n| `DOTNET_GCHeapAffinitizeMask`, `DOTNET_GCNoAffinitize` | GC threads affinitized to all cores | A core mask, or `1` | Keep server GC threads on a subset of cores, or let them float, when the process doesn't own the whole machine. |\n| `DOTNET_MaxVectorTBitWidth` | Automatic (`Vector\u003CT>` is at most 256-bit) | `512` | Allow 512-bit `Vector\u003CT>`. Decimal. |\n| `DOTNET_ThreadPool_ForceMinWorkerThreads` | Processor count | e.g. `0x40` | Minimum worker threads created without delay. A stopgap for [thread pool starvation](#thread-pool-starvation) or bursty blocking work. |\n| `DOTNET_TC_CallCountingDelayMs` | `100` ms; longer on a single processor | `0` | Only for short-lived console tools where JIT warmup takes too long: remove the tiering delay while retaining the normal call-count threshold. Can increase compilation work early in execution. |\n| `DOTNET_TieredCompilation` | `1` | `0` | Alternative for short-lived console tools where JIT warmup takes too long: disable tiering and JIT optimized code on first use. Removes tiered warmup but disables dynamic PGO and can increase initial JIT cost. |\n\nUsing NativeAOT or disabling tiered compilation via `DOTNET_TC_CallCountingDelayMs=0`\u002F`DOTNET_TieredPGO=0`\u002F`DOTNET_TieredCompilation=0` may significantly improve performance for short-lived console tools or benchmarks that measure the entire process rather than just steady-state execution.\n\n# NativeAOT-specific tuning\n\nRecommended reading: Microsoft's [Optimizing AOT deployments](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fdotnet\u002Fcore\u002Fdeploying\u002Fnative-aot\u002Foptimizing) article, plus the runtime's [CPU instruction-set tuning guide](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fblob\u002F9da219572c1\u002Fsrc\u002Fcoreclr\u002Fnativeaot\u002Fdocs\u002Foptimizing.md). The latter describes implementation-specific options that can change between SDK versions.\n\n> [!NOTE]\n> These are publish-time MSBuild properties: republish for each experiment. NativeAOT has no JIT or tiered compilation, so the CoreCLR JIT\u002Ftiering environment variables above do not apply.\n\n| MSBuild setting | Purpose and trade-offs |\n| --- | --- |\n| `-p:OptimizationPreference=Speed` | Optimize for execution time (`--Ot`) instead of the default size\u002Fspeed balance. A good throughput experiment, but can increase executable size and instruction-cache pressure. Also applies the framework's static PGO data (`IlcPgoOptimize`). |\n| `-p:OptimizationPreference=Size` | Optimize for executable size (`--Os`). Useful when distribution size or memory footprint matters more than peak throughput; measure the speed trade-off. |\n| `-p:IlcInstructionSet=native` | Target instruction sets supported by the build machine's CPU. Useful for local benchmarks or homogeneous deployments; no cross-architecture compilation support, and the executable may not run on older CPUs. Use an explicit instruction-set baseline for heterogeneous deployments. |\n| `-p:IlcInstructionSet=x86-64-v3` | The x64 equivalent: raise the baseline to AVX2-class CPUs (`x86-64-v4` adds AVX-512). The executable refuses to start on older CPUs. |\n| `-p:IlcMaxVectorTBitWidth=512` | Widen `Vector\u003CT>` (needs a matching instruction set). The compile-time counterpart of `DOTNET_MaxVectorTBitWidth`. |\n| `-p:IlcInstructionSet=armv8.1-a` | If `IlcInstructionSet=native` is not acceptable for you, consider explicitly raising the baseline from ARMv8.0-A to ARMv8.1-A to include LSE (crucial for atomics-heavy workloads). |\n\n```sh\ndotnet publish -c Release -r linux-arm64 -p:PublishAot=true -p:OptimizationPreference=Speed -p:IlcInstructionSet=native\n```\n\n# General notes\n\n- Consider filing a [dotnet\u002Fruntime](https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fruntime\u002Fissues) issue only for a clear, reproducible missed optimization in the JIT or libraries with meaningful measured impact. Check for duplicates, include a minimal repro and measurements, and obtain the user's approval before filing.\n- Compare equivalent execution plans before blaming the language. Consider pairwise comparisons, stronger filtering, and verification requirements before tuning code generation.\n- Don't draw conclusions about workloads from tiny synthetic benchmark results.",{"name":5,"description":7},"main","e43fdac8c1e126f73d255eb4298382c91c8440a5","catalog\u002FTools\u002FBlazingFastDotnet\u002Fskills\u002Fblazing-fast-dotnet",41,"https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills","https:\u002F\u002Fskills.managed-code.com","https:\u002F\u002Fgithub.com\u002Fmanagedcode","https:\u002F\u002Fwww.managed-code.com","Open Source Community for .NET Developers: Reliable, Actively Maintained, and Community-Driven. Made in Ukraine 🇺🇦 with ❤️","2026-11-11 19:34:28",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add managedcode\u002Fdotnet-skills --skill blazing-fast-dotnet",{"tool":65,"command":66},"bunx","bunx skills add managedcode\u002Fdotnet-skills --skill blazing-fast-dotnet",{"tool":68,"command":69},"pnpm","pnpm dlx skills add managedcode\u002Fdotnet-skills --skill blazing-fast-dotnet","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FTools\u002FBlazingFastDotnet\u002Fskills\u002Fblazing-fast-dotnet\u002FSKILL.md",true,[73],{"path":74,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":77,"sourceHtmlUrl":78},"manifest.json","REFERENCE","参考文档",236,"https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FTools\u002FBlazingFastDotnet\u002Fskills\u002Fblazing-fast-dotnet\u002Fmanifest.json",[80,116,138,167,177,203],{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":96,"publishedAt":44},"01M6DYQE1Y9FHAQHEDTVFQ3447","xtwitter-scraper","Use XTwitterScraper when a .NET application needs typed access to Xquik's X automation REST API for tweet search, user lookup, posting workflows, extraction jobs, monitors, webhooks, or giveaway draws. USE FOR: integrating the XTwitterScraper NuGet package; reviewing Xquik API client usage; modeling API-key configuration, pagination, binary exports, raw responses, retries, and webhook\u002Fevent flows in .NET. DO NOT USE FOR: unrelated stacks; direct browser automation; generic social-media strategy; or code that does not call Xquik. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FLibraries\u002FXTwitterScraper\u002Fskills\u002Fxtwitter-scraper\u002FSKILL.md",[86,91],{"categoryNo":87,"categoryName":88,"taxonomyType":89,"taxonomyTypeDesc":90},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":92,"categoryName":93,"taxonomyType":94,"taxonomyTypeDesc":95},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[97,98,99,100,36,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115],"xtwitter scraper","browser automation","workflows","run","browser","search","repository","api","Media","api client","access","edit","unrelated","webhooks","files","social","commands","modeling","webhook",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":128,"publishedAt":44},"01M6DYQ9S4PE1H6G8MCH9T21HR","sep","Use Sep for high-performance separated-value parsing and writing in .NET, including delimiter inference, explicit parser\u002Fwriter options, and low-allocation row\u002Fcolumn workflows. USE FOR: delimited data needs are performance-sensitive and allocation-aware; project needs explicit control over separator inference, escaping, trimming, and header behavior;. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FLibraries\u002FSep\u002Fskills\u002Fsep\u002FSKILL.md",[122,125],{"categoryNo":123,"categoryName":124,"taxonomyType":89,"taxonomyTypeDesc":90},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":126,"categoryName":127,"taxonomyType":94,"taxonomyTypeDesc":95},"01JBSEEDCAT000000000000036","内容创作者",[118,40,42,99,129,100,103,130,131,132,108,133,109,111,134,135,136,137,113,38],"tasks","data","parsing","parser","context","do","writer","options","project",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":144,"publishedAt":44},"01M6DZS2DNEPXCGGF6SSCW6G5V","webgpu-threejs-tsl","Comprehensive guide for developing WebGPU-enabled Three.js applications using TSL (Three.js Shading Language). Covers WebGPU renderer setup, TSL syntax and node materials, compute shaders, post-processing effects, and WGSL integration. Use this skill when working with Three.js WebGPU, TSL shaders, node materials, or GPU compute in Three.js.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fwebgpu-claude-skill\u002Fskills\u002Fwebgpu-threejs-tsl\u002FSKILL.md",[],[145,146,147,148,149,150,151,152,153,154,155,156,39,157,158,159,160,161,162,163,22,164,165,166,27],"webgpu","threejs","webgpu threejs tsl","setup","shaders","gpu","integration","applications","js","node","syntax","guide","language","three","compute","post","processing","with","renderer","materials","wgsl","effects",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":173,"publishedAt":44},"01M6DZS0BQXW2B56A289XF1X76","roslynk","Use Roslynk for semantic inspection and edits of *.cs, *.razor and *.cshtml solution files.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Froslynk\u002Fskills\u002Froslynk\u002FSKILL.md",[],[169,39,111,174,175,22,176],"semantic","razor","inspection",{"skillNo":178,"name":179,"nameZh":6,"description":180,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":181,"categories":182,"keywords":183,"publishedAt":44},"01M6DZRW6MEJCVW80K3S4XV60K","system-text-json-net11","Imperative guidance for the System.Text.Json APIs added in .NET 11: the built-in `JsonNamingPolicy.PascalCase` naming policy, and the strongly-typed generic `JsonSerializerOptions.GetTypeInfo` and `JsonSerializerOptions.TryGetTypeInfo` metadata accessors (generic overloads that return a typed `JsonTypeInfo`). USE ONLY when the user is targeting net11.0 or later and needs PascalCase JSON property or dictionary-key names without writing a custom naming policy, a strongly-typed generic `JsonTypeInfo` instead of the non-generic `JsonTypeInfo`, or a no-throw way to probe whether a type's serialization metadata is resolved. DO NOT USE when the target is earlier than net11.0, the requested behavior uses an established pre-net11 naming policy, or the user explicitly selected another JSON library.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet11\u002Fskills\u002Fsystem-text-json-net11\u002FSKILL.md",[],[184,185,186,187,40,188,189,190,191,192,193,39,194,195,31,196,134,197,198,199,200,22,201,202,27],"system","json","text","system text json net11","policy","metadata","library","serialization","behavior","user","only","probe","later","type","dictionary","naming","targeting","custom","generic",{"skillNo":204,"name":205,"nameZh":6,"description":206,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":207,"categories":208,"keywords":211,"publishedAt":44},"01M6DZRRJP3S662NFSG1RSPQQ7","process-api-net11","Provides guidance on the new System.Diagnostics.Process APIs introduced in .NET 11: Process.Run, Process.RunAndCaptureText, Process.StartAndForget, Process.ReadAllText\u002FBytes\u002FLines, KillOnParentExit, InheritedHandles, and StartDetached. USE FOR: starting, orchestrating, or capturing output from external processes in applications targeting net11.0 or later. DO NOT USE FOR: applications targeting .NET 10 or earlier, or basic pre-.NET 11 Process.Start usage without new APIs.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet11\u002Fskills\u002Fprocess-api-net11\u002FSKILL.md",[209,210],{"categoryNo":87,"categoryName":88,"taxonomyType":89,"taxonomyTypeDesc":90},{"categoryNo":92,"categoryName":93,"taxonomyType":94,"taxonomyTypeDesc":95},[104,212,213,214,100,215,152,184,39,31,196,134,216,217,218,219,200,22,220],"process","process api net11","diagnostics","start","basic","processes","output","usage","new"]