Performance

Embedded to web: what microcontrollers taught me about performance

Eight kilobytes of RAM is a strict teacher. The habits I picked up squeezing firmware onto tiny chips turned out to be exactly what my bloated web services needed.

µC
Eight kilobytes of RAM is a strict teacher.

My first serious programming job had a hard limit printed on the datasheet: 8 KB of RAM, 64 KB of flash. Not “8 KB before it gets slow.” 8 KB before the device stops working and a technician drives out to a site to find out why.

Years later I write services that run on machines with 64 GB of memory, which is eight million times more room. You would think none of it transfers. It all transfers.

Constraint makes you measure

On a microcontroller you cannot guess. There’s no profiler attached to a device sealed in a weatherproof box on a rooftop, so you learn to instrument everything: a counter here, a high-water mark there, a pin you toggle so a scope can show you how long an interrupt handler really takes.

The habit that came with me is simple and unfashionable: measure the thing before you change the thing. Most of the “optimisations” I see in code review are guesses dressed as engineering. Someone caches a value that was never expensive, or replaces a clear loop with a clever one that saves nine microseconds inside a request that spends 40 milliseconds waiting on the network.

8 KB → 64 GB
The number changed by eight million. The discipline didn’t.

Allocation is a decision, not an accident

Embedded work makes allocation visible because it is dangerous. You allocate up front, from a fixed pool, or you don’t allocate at all. Nobody calls malloc in a control loop and hopes.

Managed languages hide that cost, but they don’t remove it. In Go, the single biggest performance change I’ve made to a hot path was not an algorithm. It was noticing that a helper returned a slice that escaped to the heap on every call, several hundred thousand times a second. Reusing a buffer turned a garbage collector that ran constantly into one that barely woke up:

// Before: a fresh buffer every call, all of it garbage a moment later.
func encode(v Record) []byte {
    buf := make([]byte, 0, 256)
    return v.AppendTo(buf)
}

// After: the caller owns the buffer and hands it back each time.
func encode(dst []byte, v Record) []byte {
    return v.AppendTo(dst[:0])
}
Same output, same clarity, and roughly a tenth of the allocations.

The point isn’t that you should hand-roll buffer pools everywhere. It’s that make in a loop is a choice, and on tiny hardware you’re forced to notice you made it.

Latency is a budget you spend once

An interrupt handler has a deadline. Miss it and you drop a sample; there’s no “mostly on time.” That framing (a fixed budget, spent, gone) is far more useful for web work than the average-latency dashboards most teams stare at.

A request that must answer in 200 ms has 200 ms to divide among everything it does. Two sequential database round trips at 15 ms each aren’t “fast”; they’re 15% of the budget spent on waiting. Once you think in budgets, the questions change: what can run in parallel, what can be precomputed, what doesn’t need to happen inside the request at all.

Averages hide the customers who are having a bad time. Budgets and percentiles are about the worst experience you’re willing to ship.

What doesn’t transfer

Plenty, honestly. Embedded instincts can make you miserly in places that don’t matter, like arguing about a struct field’s byte alignment in a service that spends most of its life on a socket. Readability wins far more often on the server than it ever did on the chip, and a clever bit-packing trick that saved 40 bytes of RAM is just a bug waiting to happen when three people maintain the code.

The transferable part isn’t the tricks. It’s the posture: know your budget, know what you’re spending, and be honest about which of the two you’re guessing at.

Eight kilobytes was a strict teacher. It just turned out the lesson was never really about the kilobytes.


M

Mo Jahani

Software architect working on systems for millions of users, from embedded devices to web platforms. I write about the unglamorous engineering decisions that decide whether software lasts.

Get in touch