Donald Knuth wrote 4 textbooks in assembly language with advice about why decrement then jump saves 1 assembly instruction per loop iteration.
The fact that everyone uses his premature optimization is the root of evil remark is hilarious to me. The level of optimization in Knuths writing makes it evident that upfront efficiency effort is worthwhile. Just don't overdo it.
But don't trust me on that quote. Just open book 3 (chapter 5 and 6, sorting and searching) and go read the tape drive simulations of merge sort yourself. You can't miss the printouts, they're huge.
Those sound to me like the very definition of optimizations that are not premature though. His goal was to teach foundational elements of running programs on computers. Loops and sorts are used all over the place and improvements scale with inputs. I see a pretty big difference between solving for a problem you don't yet understand (the way I've always mentally framed "premature optimization") and establishing reasons for implementing primitives in optimal ways.
In my experience, the people who say"premature optimization it's the root of sal evil" will say that about any and all optimization until the point where all your customers think your product is a slow piece of garbage.
When I want some sw to go faster I do profile it, but I also give at least some thought to avoiding unnecessary copying of data or trig functions in tight loops. I regularly have run into people that say considering performance when selecting a data structure is premature optimization. It usually turns out they don't really want to test or profile and just want to churn through tickets. Anything that gets in the way of that bores them.
Measuring would attract even more protest because not only am I using my experience to choose (likely) more efficient algorithms, I am also spending yet more precious company time to measure its performance.
To understand you would have had to be there. Computers got faster (and then some) in the old days you would write something (or not even bother) then see it took way longer than desirable. You would rewrite and itterate over possible ways to rewrite. Sometimes you would see the light, other times you would try different approches brute force. The point where optimization was nesasary was completely obvious and 99% of modern code never needs the consideration.
If communism takes over the world and be given 30 years half the texts wouldnt make sense as it talks about something to do with capitalism? that doesnt exist.
I began my programming career on machines with performance, memory, and storage constraints no one today can imagine. Some of the necessary hacks and shortcuts from back then look like premature optimization and stupid coding today.
The Y2K “problem” gives the canonical example. In a world of vast and very cheap and fast storage, it makes no sense to save two characters in a date. But back in the ‘70s and early ‘80s when I implemented dates like that cutting those two characters over a few million records saved significant money. Disk space used to cost a lot, RAM (or core memory) used to cost a lot more.
Computer hardware is very fast now, so why does my computer lag noticeably on OS and browser operations? A facetious question, and perhaps it's not remotely a dealbreaker, but I expect better and would expect the same of my own software. I agree with GGP that too many people seem to take "premature optimization is the root of all evil" as "don't optimize until it's too late and then painstakingly get diminishing returns". There is a comfortable middle ground of the optimizing-delivering tradeoff that I think is wildly missed.
Multiple layers of complex abstractions and sloppy coding account for a lot of it. We write code as if it had no hardware constraints, so we get megabytes-size web pages running a slow interpreted language in a browser built on multiple layers of frameworks and libraries. Over the last several decades inefficient and bulky programming has consumed all of the gains in hardware, to the point that modern applications feel much slower than similar apps from the '80s.
Knuth did not have that in mind when he wrote about premature optimization.
My prediction was that we would create a cpu with a slow and limited opcode set for humans to write on line numbers. Something that is easy to work with with a good static module system with certified sniplets of code. Anything written for it would still run cirkels around higher languages despite slow clock speed. It didnt happen but still could.
I mean... Maybe? I guarantee that if you do any of the things he discusses in code that is getting reviewed in any company I have ever seen, you will get shot down for premature optimizations.
Just look into Gosper's hack someday and tell me that that would pass most reviews? (Which, I think is probably unfair? But I can't imagine many places being good with using standard int variables to represent subsets.)
> This is like asking for the title of the Bible after a psalm was quoted on a forum for religion.
That is…not a great answer, unless you want to reinforce the meme that CS is a cult. Besides, the context you mention is utterly alien to millions of people.
People get born every day, and people learn the basics every day. We should be helping them grow, not gatekeeping. Your second sentence was more than enough.
Excuse me, but we are hackers, not some fancy elitist people who think for themselves and find out things on their own using some modern internet search tools.
Rule of thumb. Don't try to generalize until you have at least 3 distinct examples.
This does a lot to find pragmatic tradeoffs between performance and abstraction. It also pushes you away from the over-abstraction that leads to the Second System Effect, as coined by The Mythical Man-Month.
In most places I’ve worked, there’s a big problem with this approach.
If you have three distinct examples, and you need to do something that requires a fourth, you won’t be able to justify spending time writing an abstraction. You should just copy it again. The three existing examples don’t use an abstraction, and they work just fine, so there’s clearly no need for an abstraction… or so the argument goes. In the unlikely event that you’re allowed to write the abstraction, you definitely won’t be allowed to reimplement the existing examples to use it now, because those modules are not in scope and we don’t have the budget to test them again.
If you don’t create the abstraction up front, you’ve lost the chance to have an abstraction.
That's a direct outcome of the contemporary fashion that code changes should be as narrow as possible and only touch existing code where its strictly necessary to their implementation.
It's not the only way of working, but it tends to suit the challenges faced by very large teams with high turnover, diffuse/negligent code ownership, many juniors/early-career contributors, and low trust.
It also produces software that resembles papier maché -- increasingly thick, disordered, and glutinous, with poor visibility and low durability.
If your team isn't subject to the challenges that make papier maché coding necessary, encourage people to escape its cargo cult. Small teams of people who trust each other, where code has long-term owners who can speak to its purpose and direction, can dynamically sculpt and rework code more like clay. And you get better software when that can be done.
A fourth hack may still be better than having to develop the four features on top of a wrong abstraction.
If you end up having a poorly fitting abstraction, then you'll be spending extra effort on doing things "properly", but not really getting any benefit out of it. You just loose the agility of hacking things quick'n'dirty, or you end up hacking around the bad abstraction, and get worst of both worlds.
The rest depends on having management that understands trade-offs between doing things properly and efficiently long-term vs getting stuff out of the door quickly (if you're a startup with a short runway, or racing to be the first to win some opportunity, shipping ugly hacks now may be the only way to survive to even have "later" to worry about).
That may be true, but it’s still a problem if you can never create new abstractions, because that prevents you from creating good abstractions too, not just bad ones.
> It also pushes you away from the over-abstraction that leads to the Second System Effect, as coined by The Mythical Man-Month.
The Second System Effect has nothing to do with over-abstraction of code - it has nothing to do with code at all. The basic premise is:
First System: We barely did anything we dreamed of and can see 100s of ways to improve things, but we had to get something to market.
Second System: We’ve learned so much! Revenue enables big picture thinking! Everything we ever dreamed of let’s design and build!
It’s fundamentally a mistake at a business/product design level. That feeling of success leads to a release of constraints and then trying to bite off far more than can be chewed.
The second system effect is the same fundamental mistake repeated at every level. From code to business.
The fact that the business mistakes are easiest to spot from outside doesn't mean that the coding mistakes aren't there. Even though that level of detail is only visible from inside of the system.
Truth! Many developers believe the coming up with abstractions is the pinnacle of sw engineering. The amount of time and money wasted on arguing about the right abstractions, following by massive refactoring needed when you discover that reality doesn't match your abstractions is immense. I have had engineers abstract all of the way down to Turning machines in an effort to find a model that would fit all of their use cases.
I have encountered the following interface in a real productive code base:
interface ILoadData<In, Out> {
Out Get(In input);
}
So essentially this says that to implement this interface you must have a function that takes in something (you decide!) and outputs something (you decide!) and it's called Get. Totally useless.
I agree that premature generalisation is a problem, but to tell whether anything is premature, you must have all the information as the person who decided to generalise or optimise. Put simply, these are rules best applied to your own projects, not to others'.
Often I see people criticise some project for being overly generalised or "over-engineered" simply because they don't have as full a picture as the project's maintainers.
I've always considered premature optimization to be a different problem than premature abstraction.
Premature optimization is something I struggle with - "hey, if I do this a different way it'll be faster/smaller!" And then I end up spending too much time optimizing one data area of code when I should just do it the easy way and move on to the next problem. I can always come back and optimize later when everything is working and I can see where the problem areas are, but the temptation is often hard to fight.
Premature abstraction is harder because abstracting existing code can be problematic and there's more of a chance to introduce bugs. It takes intuition and planning to get it right the first time around, and by the time you realize you should have abstracted something it's often too late to fix without rewriting a large chunk of your code and your teammates' code.
The fact that everyone uses his premature optimization is the root of evil remark is hilarious to me. The level of optimization in Knuths writing makes it evident that upfront efficiency effort is worthwhile. Just don't overdo it.
But don't trust me on that quote. Just open book 3 (chapter 5 and 6, sorting and searching) and go read the tape drive simulations of merge sort yourself. You can't miss the printouts, they're huge.