Start Debugging

The performance and Native AOT cheat sheet

Measure first, then pick the knob.

This pillar collects everything on the site about making code faster and smaller - profiling before you guess, allocation and Span<T> work, caching, the JIT and compression changes in the .NET 11 cycle, and the constraints Native AOT puts on your code.

What to read first

Measure first: profiling with dotnet-trace and diagnosing a managed memory leak with gcdump and dump are the two loops worth learning. Before you tune anything, tiered compilation and PGO explain what the runtime already does for free - the newest example is the JIT devirtualizing generic virtual methods.

For allocation, what Span<T> actually speeds up leads and List vs Span vs ReadOnlySpan picks the type. Shipping smaller? Native AOT vs ReadyToRun vs JIT settles the fork, what Native AOT costs you is the honest ledger, and trim-safe code is the discipline; the failure you’ll hit first is reflection-based serialization disabled, and FluentValidation 12.2’s IsInEnum fix shows a library catching up. On the server, HybridCache vs IMemoryCache vs IDistributedCache and detecting N+1 queries remove more time than any micro-optimisation, and EF.Parameter stops dynamic queries recompiling on every call, and a compiled model takes the model build off startup. In hot sync paths, waiting on a ValueTask without allocating keeps the zero-byte fast path.

What’s on this page

The list below auto-collects posts tagged with any of: performance, native-aot - which pulls in the Flutter and Dart performance posts too. Newest first.

Companion pillars: async and concurrency and the .NET 11 tracker.

Index (92 posts)

2026 / 10

  • What happens to an expired MemoryCache entry, and when is it actually removed from memory?

    An expired MemoryCache entry is not removed on a timer. It stays in the dictionary until a read touches it, a throttled background scan runs, or compaction kicks in. Here is exactly when each happens, and how to get eager eviction.

  • Thread.Sleep vs Task.Delay vs SpinWait for waiting in C#

    Use await Task.Delay for any timed wait in async code, Thread.Sleep only on a dedicated thread you own, and SpinWait only for sub-microsecond waits inside lock-free code. To wait for a condition, use a signal (ManualResetEventSlim, SemaphoreSlim, TaskCompletionSource) instead of any of the three. Measured on .NET 10.0.10.

  • FluentValidation 12.2 fixes IsInEnum() crashing native AOT apps on [Flags] enums

    FluentValidation 12.2.0, released October 10, 2026, swaps Enum.GetValues for Enum.GetValuesAsUnderlyingType inside EnumValidator, so IsInEnum() on a [Flags] enum no longer throws NotSupportedException in a native AOT binary.

  • What Is a Compiled Model in EF Core 11, and When Is It Worth Enabling?

    A compiled model is generated C# that recreates your EF Core model without running OnModelCreating. Measured on EF Core 11 RC1: model load drops from 714 ms to 215 ms at 500 entities, but the MSBuild route (which replaced EFOptimizeContext) is slower, stale models fail silently, and query filters block generation.

  • What Is Transparent Struct Layout, and Why Does a Single-Field Wrapper Struct Change the Calling Convention in .NET?

    A transparent struct is one the ABI treats exactly like its only field. .NET 11 has no such guarantee: a struct wrapping a double is passed in RCX on Windows x64 but in XMM0 on Linux and macOS. Here is the disassembly from the .NET 11 JIT on three ABIs, the P/Invoke bugs it causes, and how to write wrapper types that are safe at every boundary.

  • AddDbContextPool vs AddDbContextFactory for running EF Core queries in parallel

    AddDbContextPool hands out one scoped DbContext per DI scope, so it cannot run two queries at once. AddDbContextFactory and AddPooledDbContextFactory give you a context per call, which is what parallel queries need. Measured on EF Core 11 RC 1: the pooled factory creates a context in 342 ns and 40 B versus 17 us and 44 KB.

  • C# Dev Kit 11.0 Collapses Six Processes Into One Native AOT Binary

    C# Dev Kit 11.0 pre-release replaces six managed processes with a single Native AOT CSDevKit process and a persistent project cache. A 407-project Aspire solution is ready in 3.0s instead of 84.1s, using 316 MB instead of 2,072 MB.

  • How to wait for a ValueTask synchronously in a non-async method without allocating

    Check IsCompleted first and read the result through GetAwaiter().GetResult(), which costs 0 bytes. Only fall back to blocking when the ValueTask is still pending, and do that with a cached event and UnsafeOnCompleted instead of AsTask(), which allocates 72 to 208 bytes per call. Measured on .NET 10 and .NET 11 RC1.

  • EF.Parameter vs EF.Constant in EF Core 11 queries

    EF.Constant inlines a captured value as a SQL literal, EF.Parameter turns a literal into a SQL parameter. Keep EF Core's defaults, use EF.Parameter to stop dynamically built expression trees from recompiling on every call, and use EF.Constant only for a value with a handful of distinct values whose data is skewed enough to need its own plan.

  • Volatile.Read vs Volatile.ReadBarrier in .NET 10

    Volatile.Read is an acquire load of one location. Volatile.ReadBarrier, new in .NET 10, is a fence that gives every earlier read acquire semantics. Use Volatile.Read for flags and published references, and ReadBarrier when you need a batch of plain or non-atomic reads to complete before the next memory access, as in a seqlock.

2026 / 09

2026 / 08

2026 / 07

  • Fix: Reflection-based serialization has been disabled for this application

    This InvalidOperationException means PublishTrimmed or PublishAot flipped JsonSerializerIsReflectionEnabledByDefault to false. Fix it with a source-generated JsonSerializerContext.

  • How to build a Flutter web app with WebAssembly using flutter build web --wasm

    A complete guide to shipping a Flutter web app compiled to WebAssembly on Flutter 3.44: what the two emitted builds look like, why Firefox and Safari still get JavaScript because of the loader's wasmAllowList, migrating off dart:html for dart2wasm, the COOP/COEP headers that decide whether skwasm runs multi-threaded, and how to prove at runtime which build the browser actually loaded.

  • How to diagnose a managed memory leak with dotnet-gcdump and dotnet-dump

    A complete workflow for finding a managed memory leak in .NET 11: confirm growth with dotnet-counters, take two gcdumps and diff them, then collect a dump and use dumpheap, gcroot, and objsize in dotnet-dump analyze to find what is still holding the reference.

  • WebApplication.CreateBuilder vs CreateSlimBuilder vs CreateEmptyBuilder in ASP.NET Core 11

    Use CreateBuilder for a normal app, CreateSlimBuilder when you publish trimmed or Native AOT behind a TLS proxy, and CreateEmptyBuilder only when you want to register every service yourself. Here is the feature matrix and the gotchas that force the call.

  • Output caching vs response caching in ASP.NET Core 11: which should you use?

    Output caching is the right default for almost every server-side app in ASP.NET Core 11. Response caching only wins when your goal is to steer browser and proxy caches through HTTP headers. Here is the decision, with a feature matrix and the gotchas that force the call.

  • How to add response compression to an ASP.NET Core 11 API

    A complete guide to response compression in ASP.NET Core 11: AddResponseCompression and UseResponseCompression, the new built-in Zstandard provider alongside Brotli and Gzip, compression levels, EnableForHttps and the CRIME/BREACH risk, custom MIME types, middleware ordering, and when to let the reverse proxy do it instead.

  • How to add output caching to a minimal API in ASP.NET Core 11

    A complete, working guide to output caching in an ASP.NET Core 11 minimal API: AddOutputCache and UseOutputCache, CacheOutput on endpoints and MapGroup, named and base policies, Expire, VaryByQuery and VaryByHeader, tag-based eviction with EvictByTagAsync, cache stampede protection, ETag revalidation, and a Redis backing store.

  • .NET 11 Runtime Async Drops the EnablePreviewFeatures Flag

    As the .NET 11 previews progress toward the November release, Runtime Async has graduated: net11.0 projects opt in with a single MSBuild property, and the runtime libraries themselves now ship compiled on it.

  • What is PGO in .NET and do I need to opt in?

    PGO (profile-guided optimization) lets the .NET JIT specialize hot code for the types and branches your workload actually hits. Dynamic PGO has been on by default since .NET 8, so on .NET 8 and later you do not need to opt in. Here is what it does, how to see its effect, and the rare cases where you touch the knob.

  • What is tiered compilation and how do I reason about it?

    Tiered compilation lets the .NET JIT compile every method twice: once fast and unoptimized to get your app running, then again with full optimizations once the runtime knows a method is hot. Here is how tier 0, tier 1, on-stack replacement, and Dynamic PGO fit together, and how to observe and tune them.

  • What is trim-safe code and how do I write it?

    Trim-safe code is code the .NET trimmer can statically prove is reachable, so it survives when unused code is removed from a self-contained app. This is the practical guide: turn on the analyzer, drive every IL2xxx warning to zero, annotate reflection with DynamicallyAccessedMembers, propagate RequiresUnreferencedCode to public APIs, and replace unanalyzable patterns with source generators.

  • SkiaSharp 4.0 Ships Stable: 24% Faster GPU Rendering and a Cleaned-Up API

    SkiaSharp 4.148.0 is the first stable v4 release. GPU-heavy UIs render up to 24% faster, CPU shaders run ~6x faster, and the legacy API surface is finally retired. Here is what upgrading actually costs you.

2026 / 06

2026 / 05

2026 / 04

2026 / 02

2026 / 01

2025 / 04

2025 / 01

  • .NET Performance: ToList vs ToArray

    .NET 9 significantly improves ToArray performance using InlineArray, making it faster and more memory-efficient than ToList. See benchmarks comparing .NET 8 vs .NET 9.

All pillars Home