Skip to main content

Command Palette

Search for a command to run...

Same language. Same file. Two very different performance profiles

Updated
•2 min read•View as Markdown
Same language. Same file. Two very different performance profiles

Same language. Same file. Two very different performance profiles, and you pick which one per method.

That's the real reason I prefer .NET for payment and fintech workloads. It isn't one feature. You can start with safe, readable code and drop to low-level code only where profiling tells you to, without leaving the language or the runtime.

What makes that possible:

  • Reified generics. List<int> is a real list of ints, not boxed objects, and the JIT specializes value-type generics.

  • Dynamic PGO. On by default since .NET 8, the runtime profiles hot methods while they run, then recompiles them with real data, including devirtualizing interface calls that always hit one type. An interface-heavy design can run closer to a direct call than the source suggests.

  • Span and ArrayPool. Where profiling points at a hot path, slice with ReadOnlySpan<char> instead of Split, rent buffers instead of allocating them. No unsafe needed.

  • Source generators. System.Text.Json, GeneratedRegex and LoggerMessage move reflection and regex construction to compile time, which is also what makes Native AOT workable.

  • Built-in telemetry. Activity and Meter ship in the base library and line up with OpenTelemetry.

The honest trade-offs: GC still needs deliberate tuning on latency-critical paths, Native AOT restricts reflection and runtime code generation, and Rust still wins on compile-time data race checks.

I don't prefer .NET because it's the fastest at everything. I prefer it because I rarely have to leave it.

When did you last drop to low-level code inside a high-level stack, and what did the profiler show that made you do it?

2 views