The key aspect of Knuth's quote to me is "penny-wise and pound-foolish". That's how he ultimately described the premature optimizer -- someone haggling over saving pennies when there are pounds to be saved, and struggling to maintain their "optimized" (pay careful attention to how he used quotes here) software.
I find a lot of people often only quote a small part of Knuth's paper. It's worth realizing his paper was arguing to use goto
in order to speed up critical execution paths in software.
A fuller quote:
[...] this is a noticeable saving in the overall running speed, if, say, the average value of n is about 20, and if the search routine is performed about a million or so times in the program. Such loop optimizations [using gotos] are not difficult to learn and, as I have said, they are appropriate in just a small part of a program, yet they often yield substantial savings. [...]
The conventional wisdom shared by many of today's software engineers calls for ignoring efficiency in the small; but I believe
this is simply an overreaction to the abuses they see being practiced
by penny-wise-and-pound-foolish programmers, who can't debug or
maintain their "optimized" programs. In established engineering
disciplines a 12% improvement, easily obtained, is never considered
marginal; and I believe the same viewpoint should prevail in software
engineering. Of course I wouldn't bother making such optimizations on
a oneshot job, but when it's a question of preparing quality programs,
I don't want to restrict myself to tools that deny me such
efficiencies.
There is no doubt that the grail of efficiency leads to abuse.
Programmers waste enormous amounts of time thinking about, or worrying
about, the speed of noncritical parts of their programs, and these
attempts at efficiency actually have a strong negative impact when
debugging and maintenance are considered. We should forgot about small
efficiencies, say 97% of the time; premature optimization is the root
of all evil.
It is often a mistake to make a priori judgments about what parts of a
program are really critical, since the universal experience of
programmers who have been using measurement tools has been that their
intuitive guesses fail. After working with such tools for seven years, I've become convinced that all compilers written from now on should be designed to provide all programmers with feedback indicating what parts of their programs are costing the most; indeed, this feedback should be supplied automatically unless it has been specifically turned off.
After a programmer knows which parts of his routines are really important, a transformation like doubling up loops will be worthwhile. Note that this transformation introduces go to
statements -- and so do several other loop optimizations.
So this is coming from someone who was actually deeply concerned with performance at the micro-level, and at the time (optimizers have gotten far better now), was utilizing goto
for speed.
At the heart of this Knuth's establishment of the "premature optimizer" is:
- Optimizing based on hunches/superstitions/human intuitions with no past experience or measurements (optimizing blindly without actually knowing what you are doing).
- Optimizing in a way that saves pennies over pounds (ineffective optimizations).
- Seeking some absolute ultimate peak of efficiency for everything.
- Seeking efficiency in non-critical paths.
- Trying to optimize when you can barely maintain/debug your code.
None of this has to do with the timing of your optimizations, but experience and understanding -- from understanding critical paths to understanding what actually delivers performance.
Things like test-driven development and a predominant focus on interface design wasn't covered in Knuth's paper. These are more modern concepts and ideas. He was focused on implementation mostly.
Nevertheless, it's a good update to Knuth's advice -- to seek to establish correctness first through testing, and interface designs which leave you room to optimize without breaking everything.
If we try to apply a modern interpretation of Knuth, I would add "ship" in there. Even if you're optimizing the true critical paths of your software with measured gains, the fastest software in the world is worthless if it never ships. Keeping that in mind should help you make smarter compromises.
I'm leaning towards just going to the database multiple times, which I
think is the right move here. It's more important that I finish the
project and I feel like I'm getting hung up because of optimizations
like this. My question is: is this the right strategy to be using when
avoiding premature optimization?
It's going to be kind of up to you to develop the best judgement, taking into consideration some of these points above, as you most intimately understand your own requirements.
A crucial factor I would suggest is that if this is a performance-critical path dealing with a heavy load, to design your public interfaces in a way that leaves plenty of room to optimize.
For example, don't design a particle system with client dependencies to a Particle
interface. That leaves no room to optimize, when you only have the encapsulated state and the implementation of a single particle to work with. In that case, you might have to make cascading changes to your codebase in order to optimize. A race car cannot utilize its speed if the road is only 10 meters long. Instead design towards the ParticleSystem
interface which aggregates a million particles, e.g., with higher-level operations that deal with particles in bulk when possible. That leaves you plenty of room to optimize without breaking your designs if you find you need to optimize.
The perfectionist side of me wants to make everything optimal and
perfect the first time through, but I'm finding this is complicating
the design quite a bit.
Now this part does sound a bit premature. Generally your first pass should be towards simplicity. Simplicity often goes hand-to-hand with reasonably fast, faster than you might think even if you are doing some redundant work.
Anyway, I hope these points help to at least add some more things to consideration.