There has been a lively discussion on Oren’s blog with regard to whether it’s appropriate to design for performance up-front when building an application. I don’t want to rephrase what Oren is saying (he makes an excellent case in these two posts), but it essentially boils down to the following three statements:
- You can always refactor for performance later.
- If an operation takes around the magnitude of a microsecond, then it is insignificant.
- Hotspots where abstractions must be removed are rare.
As always, the correctness of the above statements highly depends on the environment you’re in and the application you are working on. It’s true that some applications can be constructed without considering performance up-front, and yet yield sufficient results. For example, most of my demo applications that I use when teaching .NET are constructed without considering performance up-front, and yet everyone is perfectly happy about using them. On the other hand, sometimes you’d rewrite code multiple times just because you didn’t design for performance up-front. And yes, it’s fun to do (the rewriting) but sometimes you just can’t afford doing it.
By the way, if refactoring and rewriting is a valid approach, and the time required to perform these activities is insignificant, then I could argue the following:
It is often not important to design an application for correctness up-front. We could implement the incorrect behavior (which is easier to code, maybe?), and if something goes wrong then we could always rewrite it. Besides, it might just be the case that the customer really wanted the incorrect behavior. (Oh, and we have a decoupled approach so it’s easy to replace just the component that yields the incorrect behavior.)
Can you spot the absurdity? Can you spot the similarity? I sure can, and that’s why I constantly fail to understand how performance is so different from correctness as far as design, development, testing – the whole lifecycle – is concerned. Performance is just another aspect of correctness. If a user clicks a button and the application crashes, it’s a problem. If a user clicks a button and the application takes 5 seconds to respond when it should be unnoticeable, it’s a problem. It’s the same problem.
As for abstractions and hotspots where these abstractions must be removed, I couldn’t disagree more. It doesn’t occur to us often because we rely on frameworks so much without ever looking under the engine hood, but lots of the infrastructure code you’d find in a framework can be categorized as a hotspot where you should be giving up abstraction and decoupling for the sake of performance.
Consider adding floating point numbers. Yes, I could design a mockable, testable approach with interfaces. Or I could make it a hardware intrinsic. Guess what, processors with floating point emulation in software are nowhere to be seen. Extinct. For a reason.
Consider taking a spinlock in the operating system code. Yes, I could design a mockable, testable approach with interfaces. Or I could make it a single DWORD and employ BTS (or XCHG) to do the work. And I could compile a different flavor of the kernel for the single-processor case, where just raising the IRQL is enough. Hey, it’s a spinlock. I’m taking millions of these spinlocks per second at times. I don’t care if it’s testable (I’m pretty sure 30 years of programming have gotten spinlocks right), I want it to be fast. And yes, if it takes a microsecond then it is SO SLOW that my best hope would be using my OS as a Commodore64 emulator. And don’t forget that any decently-sized system has billions of operations each taking under a microsecond. These things tend to add up.
Consider adding elements to a queue. Yes, I could design a mockable, testable approach with interfaces. Or I could use a lock-free queue where I don’t have the Count operation on a queue because it doesn’t align with the non-blocking implementation. How on earth do you refactor an application so that it doesn’t use the Count operation on a queue, after you have written it?!
I could go on with the examples, and what I do recognize is that for each example you could come up with a counter-example. You could also try undermining the validity of my examples. But ignore the examples – look at the underlying theme. The theme is that performance is not less important than correctness. It has to be considered through every phase of the development lifecycle. It cannot be neglected, it cannot be ignored, it cannot be “refactored into the app” later.
We all have performance built into our mindset. A good programmer makes performance-related decisions (design or coding time) automatically. It is effectively impossible to ignore performance considerations, just as it is impossible to write incorrect code when you know that it’s not going to do what you want it to.