Performance Best Practices
.NET 8 delivers impressive throughput, but performance still requires discipline: avoid allocations in hot paths, use Span<T>, pool objects, cache wisely, and profile befo…
Introduction
.NET 8 delivers impressive throughput, but performance still requires discipline: avoid allocations in hot paths, use Span<T>, pool objects, cache wisely, and profile before optimizing.
Tools: dotnet-counters, dotnet-trace, BenchmarkDotNet, Application Insights. Interviewers ask string concatenation cost, LOH, GC generations, and when ValueTask helps.
The story
A log analytics service counts newline characters in multi-gigabyte CSV uploads streamed from customers. Loading the entire file into a string would exhaust memory; instead it rents an 8 KB buffer from ArrayPool, processes each chunk as a ReadOnlySpan<byte>, and returns the buffer to the pool in a finally block — keeping GC pressure low under sustained load.
Understanding the topic
Key concepts
- Allocation pressure triggers Gen0/Gen1/Gen2 GC.
- Span
stack-friendly slicing without heap alloc. - ArrayPool
rents buffers for temporary work. - StringBuilder vs string + in loops.
- ValueTask reduces Task allocation when sync complete common.
- Response caching and output caching ASP.NET Core.
Step-by-step explanation
- Profile to find actual bottleneck — don't guess.
- dotnet run -c Release for realistic perf.
- BenchmarkDotNet compares micro-optimizations statistically.
- EF AsNoTracking + projection reduces materialization.
- HttpClient pooling via factory reuses connections.
- CompileRegex source generators (.NET 7+) for regex hot paths.
Practical code example
ArrayPool buffer for temporary byte processing without LOH churn:
namespace TechLearningPro.Performance;public static class CsvByteProcessor{public static int CountLines(ReadOnlySpan<byte> data){var count = 0;foreach (var b in data)if (b == (byte)'') count++;return count + (data.IsEmpty ? 0 : 1);}public static void ProcessStream(Stream input, Action<ReadOnlySpan<byte>> handler){var pool = ArrayPool<byte>.Shared;var buffer = pool.Rent(8192);try{int read;while ((read = input.Read(buffer, 0, buffer.Length)) > 0)handler(buffer.AsSpan(0, read));}finally{pool.Return(buffer);}}}
Line-by-line code explanation
CountLines(ReadOnlySpan<byte> data)scans bytes without allocating a substring per line.foreach (var b in data)iterates each byte in the span view.if (b == (byte)'\n') count++increments the counter on newline characters.return count + (data.IsEmpty ? 0 : 1)accounts for the last line when the file lacks a trailing newline.ArrayPool<byte>.Sharedprovides a shared pool of reusable byte arrays.var buffer = pool.Rent(8192)borrows an array at least 8192 bytes — may be larger.input.Read(buffer, 0, buffer.Length)fills the buffer with the next chunk from the stream.handler(buffer.AsSpan(0, read))passes only the bytes actually read to the callback.finally { pool.Return(buffer); }returns the buffer to the pool even if an exception occurs.
Key takeaway: ArrayPool.Return in finally. Span processing avoids intermediate strings. Rent size tuned to typical chunk.
Real-world use
Where you'll use this in production
- High-throughput JSON API optimization.
- Real-time telemetry ingestion pipelines.
- Game server tick loops minimizing GC.
- Batch ETL processing millions of rows.
Best practices
- Measure first with profiler and benchmarks.
- Prefer allocation-free Span APIs on hot paths.
- Cache immutable data with IMemoryCache judiciously.
- Use compiled EF queries for repeated shapes.
- Enable HTTP/2/3 and response compression.
- Avoid sync-over-async blocking thread pool.
Common mistakes
- Premature optimization without data.
- Micro-optimizing cold admin paths.
- Caching everything causing memory pressure.
- Logging Information in inner loops.
Advanced interview questions
Q1BeginnerGen0 vs Gen2 GC?
Q2BeginnerStringBuilder when?
Q3IntermediateSpan benefit?
Q4IntermediateValueTask when?
Q5AdvancedOptimize API returning 1M row export.
Summary
Profile before optimizing; BenchmarkDotNet for micro-tests. Reduce allocations with Span, ArrayPool, ValueTask. EF projection and caching address common bottlenecks. Release configuration and connection pooling matter. Next: interview capstone.