"Avoid Premature Optimization" Does Not Mean "Write Dumb Code"

First there's a flurry of blog entries citing a snippet of a Knuth quote: "premature optimization is the root of all evil." Then there's the backlash about how performance needs to be considered up front, that optimization isn't something that can be patched in at the end. Around and around it goes.

What's often missed in these discussions is that the advice to "avoid premature optimization" is not the same thing as "write dumb code." You should still try to make programs clear and reliable and factor out common operations and use good names and all the usual stuff. There's this peculiar notion that as soon as you ease up on the hardcore optimization pedal, then you go all mad and regress into a primitive mindset that thinks BASIC on an Apple ][ is the epitome of style and grace.

The warning sign is when you start sacrificing clarity and reliability while chasing some vague notion of performance.

Imagine you're writing an application where you frequently need to specify colors. There's a nice list of standard HTML color names which is a good starting point. A color can be represented as an Erlang atom: tomato, lavenderBlush, blanchedAlmond. Looking up the R,G,B value of a color, given the name, is straightforward:

color(tomato) -> {255,99,71}; color(lavenderBlush) -> {255,240,245}; color(blanchedAlmond) -> {255,235,205}; ...

That's beautiful in its textual simplicity, but what's going on behind the scenes? That function gets turned into the virtual machine equivalent of a switch statement. At load time, some additional optimization gets done and that switch statement is transformed into a binary search for the proper atom.

What if, instead of atoms, colors are represented by integers from zero to some maximum value? That's easy with macros:

-define(Tomato, 0). -define(LavenderBlush 1). -define(BlanchedAlmond, 2). ...

This change allows the color function to be further, automatically, optimized at load time. The keys are consecutive integers, so there's no need for a search. At runtime there's a bounds check and a look-up, and that's it. It's hands-down faster than the binary search for an atom.

What's the price for this undefined amount of extra speed? For starters, colors get displayed as bare integers instead of symbolic names. An additional function to convert from an integer to a name string fixes that...well, except in post-crash stack traces. The easy-to-read and remember names can't be entered interactively, because macros don't exist in the shell. And the file containing the macros has to be included in every source file where colors are referenced, adding dependencies to the project that otherwise wouldn't be present.

The verdict? This is madness: sacrificing ease of development, going against the grain of Erlang, all in the name of nanoseconds.

(If you liked this, you might enjoy Two Stories of Simplicity.)