4 min readJust now
–
Over the years, I’ve written about software performance from a lot of different angles: C++, .NET, systems internals, abstractions, memory, APIs, language design, and the many ways things go wrong at scale. The specific technologies change, but what’s always struck me is how the same issues resurface again and again.
Performance engineering, at least as I’ve experienced it, isn’t about tricks or clever code. It’s about how you think. What follows are 8 core ideas that keep showing up, regardless of language or era.
1. Performance Is a Systems Problem
One of the most persistent misconceptions about performance is that it lives in lines of code. In practice, performance failures almost always come from systemic effects: how allocation int…
4 min readJust now
–
Over the years, I’ve written about software performance from a lot of different angles: C++, .NET, systems internals, abstractions, memory, APIs, language design, and the many ways things go wrong at scale. The specific technologies change, but what’s always struck me is how the same issues resurface again and again.
Performance engineering, at least as I’ve experienced it, isn’t about tricks or clever code. It’s about how you think. What follows are 8 core ideas that keep showing up, regardless of language or era.
1. Performance Is a Systems Problem
One of the most persistent misconceptions about performance is that it lives in lines of code. In practice, performance failures almost always come from systemic effects: how allocation interacts with caches, how concurrency decisions amplify contention, how memory lifetimes snowball under load.
It’s tempting to look for a slow function or hot loop, but that’s usually just the easy stuff. The real causes of the tough problems are almost always architectural: too much allocation, poor locality, implicit synchronization, or uncontrolled growth.
Once you start thinking in systems terms, a lot of “mysterious” performance problems become predictable — and preventable.
2. Abstractions Are Not Free (They Just Hide the Cost)
I’ve spent a lot of time tearing abstractions apart, not because abstractions are bad, but because invisible costs are dangerous.
Modern languages are very good at making code look simple. A call site may appear trivial while hiding allocations, indirection, inhibited inlining, or cache‑hostile behavior. The problem isn’t that abstractions have costs — it’s that developers often lose track of where the bill shows up.
When performance matters, you don’t get to pretend the abstraction doesn’t exist. You need to understand exactly what your abstraction expands into and how often it executes. Otherwise, you’re flying blind.
3. Cost Models Matter More Than APIs
APIs describe what you can do, but cost models describe what it costs to do it. Performance engineering lives almost entirely in the latter.
Two APIs can appear equivalent and differ by 10× in practice because one allocates per call, touches cold memory, or serializes work that looks parallel at the surface. Without a cost model, performance discussions quickly degrade into argument by anecdote.
Good performance engineers reason about things like:
- Allocation rates
- Cache behavior
- Memory bandwidth
- Synchronization and contention
- Work per unit of input
APIs are only meaningful when you understand how these costs compound over time.
4. Measurement Is Essential — and Still Easy to Misuse
Profilers are indispensable tools, but they don’t absolve you from thinking. Data always needs interpretation, and it’s remarkably easy to draw the wrong conclusion.
Common mistakes include:
- Measuring unrealistic workloads
- Optimizing microbenchmarks instead of real systems
- Treating the hottest function as the root cause rather than a side effect
A profiler tells you where time is being spent, not why. Without a mental model of the system, profiling devolves into whack‑a‑mole optimization. Microbenchmarks magnify particular problems by design and hence tell you little about real workloads. Hot functions are not necessarily the problem at all — browser layout is always hot, that’s what it **does — **but what’s driving all those layouts?
Focus on measurements that are probative. Invariably these are the consumption metrics.
5. Memory Is the Real Bottleneck
Across languages and platforms, memory keeps reasserting itself as the dominant constraint. Modern CPUs are fast; memory systems are comparatively slow, and the gap keeps widening.
Allocation patterns, cache locality, pointer chasing, and memory lifetime determine performance far more often than raw instruction count. This is why systems that “should be fast” sometimes aren’t — and why small allocation changes can have outsized impact.
If you ignore memory behavior, you will eventually lose, no matter how clever the code looks.
6. “Premature Optimization” Is the Wrong Lesson
“Premature optimization is the root of all evil” is one of the most frequently misapplied quotes in software engineering.
The real problem isn’t optimizing too early — it’s designing systems without understanding cost at all. Choosing an architecture that requires excessive copying, synchronization, or allocation isn’t restraint; it’s premature pessimization.
Good design incorporates performance naturally by making costs visible and bounded. Tuning comes later, but awareness must exist from the beginning.
7. Languages Shape Your Performance Outcomes
Languages don’t just affect how fast code runs; they influence how people think. This is why language choice matters more than benchmark numbers suggest.
Languages that make ownership, lifetime, and sharing explicit make it easier to reason about cost. Languages that hide these details rely heavily on discipline — and discipline is often non-existent or at least doesn’t scale.
This isn’t about any one language being “the fastest.” It’s about whether the language nudges developers toward predictable performance — or lets accidental complexity creep in unnoticed.
8. Performance Degrades Gradually, Not Dramatically
Most systems don’t become slow overnight. They decay. Yes, there can be acute problems because of regressions but those are usually fixed quickly. What kills you is the death by 1000 almost-invisible paper cuts.
Features accumulate. Abstractions stack. “Temporary” allocations become permanent. Assumptions that were true at version 1.0 quietly stop being true at version 5.0.
This is why teardown exercises matter so much. Periodically reconnecting intent with reality is the only way to prevent slow, structural performance failure.
Closing Thoughts
Performance engineering isn’t about tricks, macros, or clever use of language features. It’s about having accurate mental models, being willing to challenge comfortable abstractions, and continuously validating assumptions against reality.
The technologies change, but the ideas don’t. If anything, modern systems make these principles more important, not less.
Performance is mostly not about making code faster. It’s about understanding what your system is really doing — and why.