C# Programming Tutorial 0/45 lessons ~6 min read Lesson 44

    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…

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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

    1. Profile to find actual bottleneck — don't guess.
    2. dotnet run -c Release for realistic perf.
    3. BenchmarkDotNet compares micro-optimizations statistically.
    4. EF AsNoTracking + projection reduces materialization.
    5. HttpClient pooling via factory reuses connections.
    6. CompileRegex source generators (.NET 7+) for regex hot paths.

    Practical code example

    ArrayPool buffer for temporary byte processing without LOH churn:

    csharp
    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>.Shared provides 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?
    Gen0 short-lived; Gen2 long-lived; full GC costly pause.
    Q2BeginnerStringBuilder when?
    Many concatenations in loop; few joins use string.Join.
    Q3IntermediateSpan benefit?
    View memory without allocating subarray/string.
    Q4IntermediateValueTask when?
    Method often completes synchronously; avoid Task alloc.
    Q5AdvancedOptimize API returning 1M row export.
    Stream IAsyncEnumerable; chunked transfer; no ToList; compression; background job + download link.

    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.

    Ready to mark this lesson complete?Track your journey across the entire course.