This episode teaches how to measure and improve performance: profiling with dotnet-trace and dotnet-counters, memory diagnostics and GC tuning, Span and Memory for minimal allocations, and hot reload, tiered compilation, and AOT considerations.

A correct application is not necessarily a fast one. Episode 15 covers performance and diagnostics: how to measure application behavior with data, find bottlenecks, and apply the right optimization techniques. The main rule: measure first, optimize afterwards.
You will get to know the official .NET profiling tools, analyze memory allocations, learn about Span<T> for minimal-allocation code, and understand hot reload, tiered compilation, and when AOT is worth using. Optimization without measurement is just guessing.
The .NET diagnostic tools are installed as global tools:
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-dumpdotnet-trace, dotnet-counters, and dotnet-dump are the main diagnostic trio. dotnet-counters monitors metrics in real time; dotnet-trace collects traces that can be analyzed in depth.
Run your application and observe CPU, GC, and memory metrics:
dotnet-counters monitor --process-id 12345dotnet-counters monitor --process-id 12345 displays metrics such as cpu-usage, working-set, and the number of allocations per second. This screen immediately shows whether the application is busy with GC or allocating memory abnormally.
For deep investigation, collect a trace:
dotnet-trace collect --process-id 12345 --profile gc-verbosedotnet-trace collect --profile gc-verbose records GC and allocation activity to a trace file. This file can be opened with dotnet-trace report or tools like PerfView to find the most-allocated objects.
GC works automatically, but it can still be optimized. Key metrics:
The main tuning is reducing unnecessary allocations. Among other things, by using collections with initial capacity, avoiding closures that capture large state, and using ArrayPool for temporary buffers. Avoid setting GC aggressively without data — in most cases, reducing allocations is more effective than changing the GC mode.
Span<T> represents a region of memory that can be accessed without heap allocation:
Span<int> angka = stackalloc int[] { 1, 2, 3, 4, 5 };
foreach (ref var n in angka)
{
n *= 2;
}stackalloc allocates the array on the stack — no GC pressure at all. Span<T> works on contiguous memory and is the basis of optimizations such as high-performance parsing and serialization.
Memory<T> is the version of Span<T> that is safe for data living beyond the method (for example, stored in a Task). The practical rule: use Span<T> for short local operations, and Memory<T> when data must be stored or passed to another method. Both avoid unnecessary data copies.
Hot reload lets you apply code changes without a restart. Run it with:
dotnet watch rundotnet watch run watches source files and applies changes live. In production this feature is not used — but during development, it drastically cuts iteration time.
Tiered compilation is enabled by default: methods run quickly with lightweight JIT (tier 0), then get optimized more aggressively once they prove hot (tier 1). The result is fast startup and high steady-state performance. For workloads that need the fastest possible startup, consider disabling tiering or using AOT.
NativeAOT compiles the entire application to native code at publish time:
dotnet publish -c Release -r linux-x64 -p:PublishAot=truedotnet publish -c Release -r linux-x64 -p:PublishAot=true produces a native binary with no separate runtime. The benefits are instant startup and a small footprint — ideal for serverless and edge. The limitations: reflection is restricted and adaptive JIT performance is unavailable, so AOT is not for every workload.
Tip
Optimization starts with measurement. Profile the application with dotnet-counters and dotnet-trace before changing code — changes without data risk making things worse.
Key takeaways:
Span<T> accesses memory without allocation; stackalloc for the stack.In the next episode 16 we will discuss advanced architecture and patterns — clean architecture, layered architecture, and the modular monolith, mediator and event-driven design, CQRS and domain-driven design, and service composition and bounded contexts.