4 ms·
While I agree with the overall feelings that the author expresses -- that people misunderstand "Premature Optimization is the root of all evil"[0] and in doing
by mdip 7y ago
While I agree with the overall feelings that the author expresses -- that people misunderstand "Premature Optimization is the root of all evil"[0] and in doing so treat optimization as something that is "evil" rather than premature optimization. There are various misunderstandings, which lead to incorrect assumptions which lead to introducing complexity (which is likely to be counterproductive, potentially performance-killing) too early in the development process. I consider the following quote to be key:
> It’s never too early to start measuring and working on performance.
Personally speaking, when I start feeling this way, I see it as a strong signal that I'm engaging in optimization too early. I'm guided by the idea that time spent attempting to improve performance at early stages is more often wasted, or even self-defeating, than it is useful. Knowing exactly when is the right time, though, is extremely tricky.
Most languages/frameworks have a set of "don't do this"-es that fall into the performance category -- knowing these and implementing them when the probability that they will reduce a bottleneck vs. overly-complicate code/implementation is important. Too often, though, the "performant implementation" is used because other developers are familiar with it -- not because it necessarily helps anything (i.e. using StringBuilder in C# for string concatenation -- in many common implementations, the result is slower but people are so used to seeing it that it becomes "something many developers just do" even though it is substantially uglier).
Performance shouldn't be an after-thought. If I'm expecting that my application must support 10,000 concurrent users on a single host and the work its performing couldn't be achieved by a single-threaded server, I'm going to write the implementation thread-safe from the start. That's not premature optimization, that's designing an application. Provided the hosting constraints are not as rigid, it may make more sense to single-thread the back-end on multiple hosts. Neither of these are as easy to reason about as a single-threaded, single-host, blocking implementation, but the alternative is "it doesn't work at all" (under load, anyway).
The problem with measuring/working on performance too early is that the measurements are measurements of code in isolation. You can't measure what isn't written and once the various dependencies are introduced, your initial measurements/adjustments for performance may be pointless. Yes, you sped up the acquisition of a small blob of JSON that takes a notoriously long amount of time to produce, but because you hadn't completed the rest of the app, you neglected the fact that another request with 250MB of JSON ran at exactly the same time. You traded off memory for CPU to speed up the first implementation, but now you need a lot more memory for the one running in parallel and the lack of resources caused swapping, slowing everything down to the point that the original, slower, less-memory-consuming option for your first request was a better choice.
The "Premature Optimization is the Root of All Evil" isn't a phrase meant to say "Optimization is unimportant and you shouldn't waste time on it". It's a recognition that optimization is extremely important and will almost certainly never happen due to resource exhaustion. The confusion is about what resource is being exhausted -- the resource is time. We're all constrained by having to write functionality and make it work. If you want to make it perform well, you're going to burn a lot of time on optimization. You will burn far less time there if you optimize your code when those optimizations are going to have the greatest benefit. Optimizing too soon means you risk spending a lot of time measuring/tweaking with a result that will not improve performance, or worse, has to be undone. "Optimized" code is usually more complex/more difficult to reason about, which makes completing the underlying feature-set more difficult.
Measuring and optimizing at the right times (and identifying the correct "right times") maximizes the positive effects of the optimizations and reduce the likelihood that those optimizations will make writing the rest of the code more difficult. Every program (and kind of program[1]) has a different "right time". Determining that "right time" can simply be a matter of planning your hardware / code architecture early enough to know what is actually needed[2] and identifying when one should start reviewing the application for bottlenecks.
[0] I have much the same problem that I have with the biblical roots of that phrase and its misunderstanding. People will often repeat "Money is the root of all evil" but miss the first part of that phrase "... the love of money is the root of all evil." With appropriate context, its meaning changes dramatically.
[1] Library code vs. a complete program -- My "general libraries" are often micro-optimized to seek the greatest performance while maintaining a balance between memory usage and CPU. This makes them maximally useful to the vast majority of my consumers. I might end up ripping those out of an implementation if I find a better trade-off given a complete program's function, but most of the time it reduces the need for me to optimize around common bottlenecks
[2] Regular check-points / proper planning are as close to a cure for "the roots of all evil" as you can get. Performance is always a feature in that you know in advance what your applications constraints are and what its requirements are. Designing code that meets neither isn't abiding by the "Premature Optimization" advice; the point is avoiding writing complex implementations that eek out a few extra milliseconds of performance for a code path that is blocked by dependent-data, anyway, which is often the end result of premature optimization.