Exactly. Use simple language. State what you like / dislike about something and provide a compelling argument. Don't try to add false legitimacy with a "considered harmful" title. If your argument has a broad consensus, then state exactly which groups have come to that conclusion.
I think you are conflating "dogma" with "mental model". The accepted meaning of "dogma" is a strong belief that adherents are not willing to discuss rationally.
"Dogma" is the word we use to decribe the kind of beliefs other people have that they are not willing to discuss rationally.
Most of us will have a hard time recognizing our own dogma.
My claim is that we all have things we believe dogmatically. They for the axioms of our mental models. Take away the dogma, and our entire mental framework risks getting shattered.
If we completely fail to understand this, though, we are at risk of constructing rationalistic (as opposed to rational) frameworks on top, in believe in them absolutely. This can lead to fanatical movements such as Islamic suicide bombers, "Objetivist" capitalists, Extinctin Rebellion or Conquistador Missionaries.
And also all sorts of cargo cults in software development.
>> My claim is that we all have things we believe dogmatically. They for the axioms of our mental models.
I agree but think of it as a scalar. Some mental models are assigned a "conviction" of 1.0 - that could be considered dogma. Some (like the expectation of what tomorrow's weather might be like) may have a "conviction" of 0.3.
Sounds like a Bayesian Network, which is precisely how I tend to think about it.
Though I think one aspect is missing, namely something describing the emotional loss that will be felt if a dogma has to be rejected.
In fact, the most destructive dogmas may be those where the older has serious subconscious doubt about the validity of the belief, but where their entire identiy may be shattered if they have to give it up.
Questioning such dogma tends to become very strong taboos or potentially heresies worthy of cancelling or even death.
“Systems that are designed without any thought of performance rarely perform well, even with an extensive optimization phase at the end of the software development cycle.”
I’d go further, if performance is not part of your initial requirements you’ll never get it.
Performant systems come from architecture and data modeling. Heck in the modern era data load patterns Trump algorithmic efficiency even. Those things are very challenging to back into a codebase.
Not sure how I feel about this article... there are some valid points, but also some half-truths, where the answer is more "it depends" than yes or no.
The feeling I get is that the article tries to make a long list of all possible causes, which dilutes the main reasons why premature optimisation or even optimisation is skipped. It also doesn't try to see the issue from the other side and inform the reader when optimisation is premature and doesn't need to be considered, which makes its argument very one-sided.
One additional point that I think might be missing from this list imo, is the shifting functionality requirements given to the developers which make it less certain that a specific piece of code will be the foundation on which you will build the rest of the logic or whether it will be a leaf-level logic that will be used very infrequently but this comes with other caveats regarding refactoring, etc.
The thrust is that Hoare's statement has been misapplied (I agree!) but even moreso: just because someone said it, and someone famous repeats it, does not mean it's true.
The statement is really a bit of a tautology: what is 'pre-mature' afterall? Well, it's any optimization that is not going to bear fruit for the user.
So the statement might as well be 'optimizations which do not improve the user experience do not improve the user experience'.
I see these sort of statements a lot. 'dont add too much salt', or 'dont drive too fast'. They dont really say much other than 'there is some max point which should not be exceeded'.
I guess my point is that it doesn't really guide us. Of course any 'pre-mature' optimization is not useful. The difficulty is knowing whether it's pre-mature, which can only gained through experience.
I think the reality is that optimizing code is not-fun. Adding features is fun. Writing tests is not-fun. If given the chance, we will find excuses to avoid the not-fun parts of software.
I think the reality is that optimizing code is not-fun. Adding features is fun. Writing tests is not-fun. If given the chance, we will find excuses to avoid the not-fun parts of software.
To what is unfortunately a minority, it's the exact opposite. I enjoy optimising existing code more than adding new code.
My gosh man the things I tried to do as a young Eng. myself - some of the tedious things I did to avoid copying strings or this or that! It's hard to see how such things add to 'complexity' in a way that slows down the project.
'Keep it Idiomatic' is a great way to keep things moving and as things become problematic, they can be tweaked.
I have never, in 18 years of professional programming, heard "premature optimization..." used to mean that "you should never optimize your code!" Not saying it never happens, but I have never heard it once used that way.
I do occasionally hear it used to mean "don't spend the time to optimize until you have measured whether this is your bottleneck and it will actually matter", or "get it working first so that you know this is actually what you even want, otherwise you will end up optimizing things that get cut soon after". Both of which are valid.
I've personally, in my 25 years of professional programming, heard it many times to mean "I don't want to consider performance at this stage of development, I want to move ALL optimization to when the code is done."
Typically after I've made comments on their design very early in the development process about the code being doomed to be very slow, in a manner that cannot be solved by optimizing small parts of the code later (so will require a rewrite).
And I'm not talking about the small wins, that make things 10-50% faster. I'm talking about design mistakes that slow down the code by several orders of magnitude. (Big-O stuff), and that is represented at a point in the design that is so fundamental it's hard to fix without scrapping everything.
An example might be images used as thumbnails in the UI. If they are fetched over the network, you might as well plan to have a local image cache. No need to wait on that one - and I would not call that premature optimization. Perhaps others would though (ha ha, and perhaps they’re right if there is already a cache in some other hidden part of the pipeline - yes, I am undercutting my own argument).
> if there is already a cache in some other hidden part of the pipeline
Still counts as a win, you can IME take out a cache mechanism easily. If somebody wants to ding you for writing excess code I don't know what to tell you, they are probably a-holes.
The problem is that in real world, you have to assume corners will be cut. Deadline pressure is the one constant, and Parkinson's law reigns supreme[0]. You may reason that a local cache is a premature optimization, then what happens is that those UI thumbnails get shipped in the prototype quality, they end up blocking the UI loop on network, and nobody ever goes back to implement local caching, because there are always new features with new deadlines on them.
Postponing optimization is fine, but if everyone is just postponing it, and nobody is treating it as priority, then it just never gets done. In most software, low performance is effectively an externality - it's unlikely to be the thing that kills your product/business[1], but it's causing real frustration in people and waste real money in the overall economy.
[1] - Or at least not obviously so. Performance issues often masquerade under general complaints of bad UX, or the software feeling "off" somehow. Also performance is often a limiting factor in use cases; it can be what enables you to access a whole new market segment, or deny it to you.
Rich Hickey has a succinct quote that reveals the abuse that Hoare's "premature optimization" quote has undergone: the cheapest place to fix bugs is in design.
And if we're going to proceed by quotation, another one to consider is Linus Torvalds': Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
IMHO, a big thing for junior programmers to unlearn is that one should begin by putting a sloppy version of the program in production, and incrementally improve it thereafter. A little bit of forethought and a bit of attention paid to big-O will do three things: (1) make incremental improvement easier (2) limit the amount of incremental improvement needed, and (3) avoid painting you into a corner wherein significant refactors are needed.
As it turns out, thinking ahead of time is not antithetical to shipping code. Quite the contrary.
What has to be 'unlearned' is that Engineers think they have a clear vision of what they need to do ... but in reality they don't.
This largely has to do with 'unknown unknowns', particularly in requirements.
It's actually a much harder concept to learn/unlearn - because we are trained to design towards specs, towards a kind of perfection, to solve problems in which the conditions are contrived.
Almost nothing in the academic world prepares people for having to build something whereupon the objectives are a bit fuzzy - especially when said objectives are not presented as fuzzy.
It almost requires the 'experience of failure' to grasp that most things that can be incrementally built - should be.
The process of building something, is actually a process of 'discovery'.
I think that the only time 'prepping far ahead' becomes useful, is when Engineers are building on very foundational, learned knowledge derived from a relevant situation, in which case they are actually acting in an iterative fashion, leveraging knowledge from the previous 'situation' - but even then, I would not consider that for optimization, rather, just keeping a lid on arbitrary complexity.
And finally, of course some things are reasonable. You probably are not going to build a high performance codec in Python.
Agree with almost everything above, except the first line.
In military strategy there is the quote "Plans Are Worthless, But Planning Is Everything" [1].
I think the same goes for software design. To believe that one can do a complete design of some piece of software, and then implement it precisely as planned, is folly, just as for military plans.
Still, it is imperative to do some deep thinking about the purpose of the software as one starts coding, especially about things like the purpose of the software (and what is NOT the purpose), design and design philosophy and expected challenges (including performance challenges) including ideas about how to find solutions to those challenges.
Also, after starting to code, it's important to revisit these aspects. Is the purpose changing? If it is, is that good or bad? How do we define the new purpose? Are we making changes to the design? Why? Do we need to refine the design concepts? And so on.
Just like for military strategy, this is an art more than a science. We have strong limitations dictated by what is possible, yet it is essential to have an overall idea of what direction we want to move in.
Otherwise we, more likely than not, end up with a useless patchwork.
I think a part of this is that engineering concepts are not often introduced in a lot of CS courses. It is more often introduced in software engineering courses... of which there seem to be few?
In my experience it's not well known that requirements are different than specifications and what those differences are. Knowing the difference makes understanding why planning is useful but "plans" are useless.
Requirements state the desired properties of a system that affect our world. They are written from the perspective of humans interacting with a desired system to achieve some goal. It's useful to know that we need a system that does X so that we can Y. We need to ensure that Y eventually happens when the user initiates X. If the system cannot succeed at Y within Z minutes we need to notify the user so that they can initiate procedure J and halt the process manually.
Requirements don't have to tell us how to implement such a system. They're more like the end state. It's the job of specifications to tell us, technically, how such a system operates.
We, the developers building the system, don't have to write the full specifications up front. That's a bit of a mistake. Because the gap, as the GP points out, is quite large from the starting point and the end result. It's quite normal to start with the bare minimum specification and iterate towards an ideal one. As you learn about the problem space we can build more certainty about how such a system can be implemented. We can update the specifications, verify the code implements the specification, and hopefully in the end arrive at a system that fulfills our original requirements.
It's rare that one can write the full, complete specification and leave it up to the developers to implement it.
Thanks for that comment, but one small thing - in the military it's really not 'planning' so much as it is 'training' (or 'preparing').
This actually makes the analogy a bit closer to startupland.
The #1 factor in terms of predicting project outcomes is the 'quality of the people', so 'training' speaks to that.
This is why I believe good early stage startups are so often focused on teams, not business models, because the later will change as the team orients dynamically towards 'something that works' and the analogy is right there for software as well.
Embarrassingly, I'm working on a project now that would have been done 4x quicker were I to have known at the start what I do now! But that's how it works.
This is quite a bit different than 'premature optimization' however ... but maybe it's related in terms of idioms and architecture, which are derived from experience.
A good dev/architect can probably identify the 'roughly appropriate architecture' very quickly for something, which is the expression of a lot of built-in knowledge, and might encapsulate a lot of things. Aka: 'yeah we don't need protobuff for that or anything fancy, it's not worth it, just use json over http and you're good' etc.
> , but one small thing - in the military it's really not 'planning' so much as it is 'training' (or 'preparing').
I think this depends a lot of the rank as well as the type of conflict. At low ranks and in a strict C&C doctrine, I suppose drill, practice and discipline play a huge role At higher ranks or higher levels of autonomy, war becomes way more of a mind game, while motivation may be an ok replacement for discipline.
When I used the term "Military Strategy", I meant primarily the operational level and above, or basically from a division commander (Mj.Gen.) and up, as well as the general staff.
At the tactical level, war becomes more of a science or craft, governed by doctrine and training. Higher ranks, though, are more likely to encounter the novel and unexpected, especially if facing an enemy of approximately equal strength.
> The #1 factor in terms of predicting project outcomes is the 'quality of the people', so 'training' speaks to that.
Training is important, for sure, and if you are in a position to only hire the very best, so much the better.
But technical leader will still need to figure a lot of stuff out about emerging trends and technologies as well as find litterature and other writings (or videos) for more established domains (that are new for their company). For these roles, there are a lot of ways to screw up, whether through stagnating or going all cargo cult based on a trend they don't fully understand.
What has been much, much more worrisome in my practice, is ‘unknown knowns’ which is something developers should know but do not. Poor design and engineering skills, low level of professionalism is what keeps plaguing software industry. (Remarkably, most other industries, those that have to deal with material things, do not suffer from this - not on this scale anyway.)
In my experience refactors and big-O thinking tend to show up in pretty different places. Refactors are more about which components are talking to each other, and what is the content and shape of that communication. Big-O is more about how a component does its work internally.
Both of the quotes in your comment resonate with me, in the sense that they can avoid unnecessary refactors. But IMO the big-O/performance part should really mostly just be done on a by-need basis (i.e. you've identified your bottleneck and determined its worth the time to improve it). I've seen way too much unreadable code because "otherwise it wouldn't be performant" in places where performance doesn't matter, and similarly I've seen people sacrifice a clean modular interface for the sake of performance.
Sure thinking ahead of time makes sense, as does choosing a design which puts flexibility in the right places (i.e. gives you room to adapt to what you learn in the future which is currently uncertain). But that same uncertainty also means you're probably not going to get the design fully right now, so don't overthink it.
> But IMO the big-O/performance part should really mostly just be done on a by-need basis
I agree
>(i.e. you've identified your bottleneck and determined its worth the time to improve it).
For this, though, it is often possible to identify this BEFORE writing any code. As opposed to refactoring an inner loop, Big-O challenges may often stem from (at least in my experience) the top level design of the code, and changing it may require rewriting almost everything.
This is particularly true in a microservices world, where the problem may often be how you split a computation task up into services in the first place.
Big-O is often used for speed, but it applies to anything satisfying Blum's axioms. API requests are a good example, especially when using a throttled or pay-for API.
I was in a design discussion this morning for some new functionality which requires hitting a pay-for API; and much of the design was based on minimising how many calls we'll make. A naive polling approach would require O(t×e×u) requests, where t is time, e is the number of the events we care about and u is the number of users affected by those events. The design we came up with uses a separate data source to identify when those events occur, which removes the factor of t. We also came up with some de-duplication logic which reduces the constants.
You can easily identify natural caching locations and mock quick tests to approximate ballpark time costs. Existing code is likely to be helpful for overall estimates and you can consider relative performance of user terminal to your own test bed. All sorts of ways to get informed on basic performance parameters ahead of time.
I find BigO performance is very rarely the cause of bad performance. It's normally slow IO, timeouts, deadlocks etc
I think I've actually seen performance decreases by people trying to decrease BigO. BigO only starts to really dominate time when N reaches higher levels. At lower levels of N your performance on your constant operations matter more, and by implementing more complex algorithms actually decreased real time performance. Obviously a lot of nuance in this. But first you need to measure.
I work on an application with some serious performance issues. Nearly all of them involve some combination of:
- inefficient design around data dependencies and their identification
- redundant work performed around said dependencies
- excessive reliance on APIs for uses they’re not intended, which frequently causes…
- …excessive VM deoptimizations and excessive GC
Granted this is one application and I certainly don’t think it’s representative of anything. But it’s definitely a counterpoint to inefficient concurrency (where this app generally performs quite well).
Knowing WHEN to do BigO optimizations is part of knowing how to optimize. And for cases that _could_ ble NlogN, it may be perfectly fine to chose N^2 for moderate N, while N^3 can kill the solution.
As for timeouts, IO and deadlocks, those problems can also be a case of poor up front optimization (before codigng).
I find that big-O is exactly the performance problem that remains after you've tuned your IO, picked sensible timeouts, and eliminated deadlocks (which are straight-up bugs).
The things you list are the low-hanging fruit, after which most engineering teams throw their arms up and say "I guess we've gone as far as we can on this!"
Big O can mean a lot of things. People often only think of BigO in terms of cpu cycles but it can be applied to memory and network just as easily.
Consider a piece of code that grabs a set of records and then loops over them to get an association for each one. I've seen this kind of code in code reviews from juniors. its a good starting point where I start showing them how to rewrite their sql call to get the associated record via join so it goes from O(n) network calls to O(1)
Yeah, not sure if it's a mental roadblock but it's painful to incrementally improve something especially if you've learned of better ways (which happens a frequently as a jr) as you end up fighting with your own idiot self.
Bad code is laid down at day 1 and everything past that is just worsening it even if it appears to be a positive refactor. Torch the canvas and embrace the cycle.
Not that it has to be preplanned, per se, as the absence of planning before day 0 still becomes plans on the day IME.
I find the key is to always write your published code so that it's intention is clear. This doesn't require knowing what the ideal code is or an attempt to have all the requirements pinned down, and it can result in code that is "sloppy" as in "not aligned with what we actually want," -- but we should avoid being sloppy in terms of "this is what I intend this code to do."
Requirements and understanding change, and so code should change as well, and this happens with greater frequency earlier in a project. But code that clearly expresses its intention makes that much easier -- someone reading it (maybe even the original author!) can better see "does this code actually do, what it thinks it does, does it do what we need it to do".
Optimizing for speed or memory footprint might or might not be premature optimization. It's not always easy to tell. But optimizing for _clarity_ is never a premature optimization.
Optimization gets treated a lot like AI: When we find a concrete use for some facility of it, we give it is own name and disassociate it with the source.
What once was Optimization gets called Information Architecture, Capacity Planning, and so on, and all of these things should be done as soon in the project as possible.
> IMHO, a big thing for junior programmers to unlearn is that one should begin by putting a sloppy version of the program in production
This is the closest we've gotten to a Just Do It mantra but we have a lot of space for improvement. An instructor can't help you get better at public speaking if you won't speak at all until you can speak well. However once you can speak well you shouldn't rush important speeches because you can't walk some things back once they're out there.
We will always have a steady stream of brand new developers who need simple rules of thumb, but some senior developers never grow out of it. What I see is a lot of these aphorisms being used as a way to censor others who might be suggesting work in the "hard but valuable" quadrant of cost vs value.
At some point in my career I got so fed up with coworkers trying to go against business needs (customers are complaining about responsiveness) by shaming coworkers into not touching performance and essentially running a work slowdown, that I began identifying types of code refactoring that improve clarity and speed. Sometimes what confuses the developer confuses the compiler. Particularly in the realm of strength reduction.
A lot of coding decisions come down to that elusive idea, judgement.
Think about what the pros and cons are, what the scope is, budget, and... decide what to do. I've never come across one of these articles that doesn't boil down to "think about it".
That's not to trivialize it, because of course it can take years to hone your development judgement powers, but essentially such articles are only useful so far as they tend to contain a long-form list of commonly encountered thinking points.
More specific experience gives you more detailed items on your list. Level 0 of the performance considerations (relevant to every vertical) is maybe whether you can just throw more hardware at it. In my little vertical (finance) you might think about what data structure to use based on what instrument type you're building a system for.
Here's the problem: If you pick a poor framework, pattern, or abstraction at the beginning, and leave optimization to the end... You end up needing to do a significant refactor or rewrite.
Translation: Don't suck the entire DB into RAM to read a single value.
Worse is better vs plan carefully is this balance that shows up in every creative field. It’s in writing, in physics, art, everything. Do you try and think carefully up front about what you want to and engineer carefully against what you imagine the requirements to be, or do you hack and slash your way to your goal? Not only is there no right answer, but the right answer changes day by day, hour by hour. It’s the central meta-decision. Making this decision with good judgement is one of the key skills of a good engineer. Someone who applies “worse is better” regardless of the context is a bad engineer. Someone who never finishes anything because they’re overthinking and overabstracting is likewise a bad engineer.
> Observation #6: Software engineers have been led to believe that their time is more valuable than CPU time; therefore, wasting CPU cycles in order to reduce development time is always a win. They've forgotten, however, that the application users' time is more valuable than their time.
This is the key thing. I can hardly think of single application where my number one feature request isn’t “make it faster”. Rarely am I interested in a “fresh new look” or “more buttons”. If you can make some task take 5 seconds instead of 10, that is a win for everyone.
YMMV. I, for example, work in a field where for customers, as long as performance is good enough, features-features-features is the number one priority.
Visual Studio Code is actually a good example of "performant enough" software; it's famous (appealing) for its extensibility, but certainly not performance. Then there are cases like Slack or GNOME 3, which were adopted even with lackluster performance. Go figure :|
> I, for example, work in a field where for customers, as long as performance is good enough, features-features-features is the number one priority.
There are fields like that. Then there are fields that seem to be like that, but where it's just the person making the purchase decision that cares about "features-features-featues", and actual users don't use most of them and would instead prefer faster software with sane UX.
I've definitely seen more of the latter than the former in my life.
> Then there are cases like Slack or GNOME 3, which were adopted even with lackluster performance. Go figure :|
Nothing to figure out here, it's about who is making the choice, and what choices are available on the market. Slack case is the very common situation of the choice being made by few people (not necessarily best suited for making that choice) for the whole organization / community, and heavily influenced by all the marketing spend and dirty tactics Slack the company did. GNOME 3 is the case of a supplier-driven choice: i.e. some distros defaulted to it for whatever reasons, and people mostly had to accept it, because WM is one but many qualities you care about in a distro, and alone not enough to make you choose a different one.
Both types are common situations on the market, and not at all limited to software. To again name them, in short:
1. People making the choice are not the end-users of the product/service.
2. Choice is made by the supplier, and the market doesn't provide an equivalent alternative that made a different choice.
>> It's just the person making the purchase decision that cares about "features-features-features"
I think it can also be management layer driven. Making something a little bit faster is so much less visible than adding a new, observable / obvious feature.
You also have to multiply time savings by the number of users. If some optimization will cost a developer an hour and that will result in only one minute savings for a single user, then that might seem like a loss; but only if you have less than 60 users. Once your software has a million daily users, every second you save results in a cumulative savings of 1 million seconds (over 11 days).
People are treating optimization as a zero or one thing. While in fact there are many middle grounds.
If we're taking Pareto principle into account, I can say that we can do 20% of optimization to get 80% of the benefit. The more experienced you are, the easier that first 20% is.
Premature optimisation is not an excuse to avoid thinking about runtime characteristics either at design or implementation time.
Premature optimisation is the junior engineer using some cargo-cultish ideas to attempt to shave a couple of instructions off a loop which a) is unlikely to be any sort of bottlneck and b) is going to be picked up by the compiler anyway if it's that simple.
It's spending days of effort making some piece of code faster when in practice that piece of code is neither executed often nor on any critical path.
"The Fallacy of Premature Optimisation" seems to me to be an article which takes the worst behaviours of people who don't believe in optimisation at all, and uses them to dismiss the entire idea that effort could be misapplied in dead-end microoptimisations that don't achieve anything.
Maybe you've been more lucky than me, but I've seen plenty of such "junior devs" in positions of influence, with titles such as "senior" or "principal" dev, "architect" or "engineering manager". In many places, titles like that accrue automatically as people get older, especially if they play the political game reasonably well.
Some of them will then happily do what you correctly label "Premature optimization" in the microcode, but proceed to make big mistakes in the overall design that leads to several orders of magnitude slowdowns in the general design.
I believe the problem is in the training. As junior devs, they may have been scolded for premature optimizations of microcode, but worked within a well thought-out design at the macro level. Then they advance in ranks, and have to do the design part as well, but they never learned to think about the performance at that level, something that benefits a lot from some contemplation up front.
Even though computing resources have grown by leaps and bounds over the past few decades; it seems that the amount of data has grown by even more. I have found that it is much easier to generate twice as much data needed for processing than it is to get the processing of it to run twice as fast.
When you multiply by really big numbers, the problem becomes very apparent. A wasted millisecond in a function doesn't seem like much until you call it a million times.
I always make performance considerations part of my specifications when they matter. Resources are not infinite! Often performances are part of the requirements even though they are implied.
Our requirement for a traffic control system might be primarily concerned with the safety of motorists and pedestrians crossing an intersection. However it's generally implied that this system should eventually allow some cars through and some pedestrians through. And that nobody is left waiting too long!
Such a system wouldn't be terribly useful if pedestrians never get a chance to cross because the light never changes.
It's not premature optimization to design your system with resource constraints in mind. It's smart engineering! A user shouldn't have input lag to scroll a document. The game engine doesn't need to render an infinite number of entities. An HTTP resource shouldn't be returning every record in the database. If you keep these constraints in mind in the design phase you generally end up with a well performing application.
Ignoring them is what I've heard called, "premature pessimization," and is a popular source of performance problems in mature applications.
I think biggest problem about "premature optimisation" is the problem that defining "premature" has been left to the reader and everybody has their own definition. Then they will spar online completely needlessly because they are comparing apples to oranges.
The important thing to remember is that the earlier in development something is fixed, the cheaper it gets. The longer a problem persists throughout the process, the more expensive it will be to fix.
And "more" here means "exponentially more". Because at the time of design or discussing with the client I may be able to fix it in a second by just choosing a different option. Once it gets to production it might be either impossible or costs millions and months of development.
The job of the person who designs it is to identify which decisions will be hard to undo later if they are found to be wrong and focus on making sure these are done correctly.
> The whole concept of "premature optimization is the root of all evil" is contrary to the software engineering maxim design first, code second.
I'm not familiar with that maxim but it sounds very waterfally. That's a maxim agile developers would have rejected two decades ago. (In fairness this article is from 2009 when agile was still relatively new.)
If I can look at a method and think of obvious speedups then it was a good method because I could easily read it and be confident about what it does.
Loads of performant code is really hard to read because it's full of magic that speeds things up. When you try to modify this code you're never sure if you need to keep the "performance" especially if there's no performance test.
I've also done speedups of legacy software. What you find is that most of it is "basically instant" and one method takes 5min. You rewrite that method to be fast, add a performance test to keep it fast, and a big comment explaining exactly why it needs to be fast and how it works.
> Loads of performant code is really hard to read because it's full of magic that speeds things up.
That's if you go down to the lowest levels of optimization. The kind of things you see in compression, hashing, sorting, codecs,... Really hardcore optimization because otherwise, you wouldn't be able to watch that video at without turning your device into an heater, if at all.
But in more reasonable cases, simpler code often turns out faster. The less you do, the faster it runs, which is kind of obvious. Readable code is also a good thing for performance, if is easier to see and fix inefficiencies if the code is readable. A good part of performance problems are simply bugs: memory leaks, operations done several times for no reason, etc... and many others are fixable with no loss in readability, for example by using the proper data structure.
As for the last part, use a profiler, there is no point optimizing without a profiler. If you don't have a profiler, then good luck, it is doable, like investigating a crash without a stack trace is doable, but it is horrible.
One thing I do no matter what: always use the algorithm with the best big-O complexity available, unless you have a good reason not to. You may think you don't have enough data for that O(N^2) or worse to matter, but it can change, in fact, this is the most common type of change, and once you get in the zone, it can get really slow really fast, lookup "accidentally quadratic".
My resistance to the term "Premature optimization" is generally not about optimizing single methods, but usually either about optimizing how to split an application up to modules (services, classes methods) or about what technology to use for each component.
For instance, if you have an "inner loop" or basic computation unit that requires you to do several REST requests to other services (serially, with little room for parallelism) where it would have been possible to maintaina few hashmaps locally in your service with the same data, you may easily take processing time from 1 hour to 1 week for some computation task.
With some experience, you should be perfectly capable of undersanding this way before defining how to split your workloads up into microservices.
73 comments
[ 2.8 ms ] story [ 149 ms ] threadThere are only two hard problems in computer science: "cache invalidation, naming things, and humorous recursion.
Just be hones and say "I think X is a bad idea"...
Dogma is the simplifications we teach to children (and students and junior devs) so they don't have to contemplate the full complexity of reality.
The problem is when we start to attach our identity to these dogma, and hold them so precious that we will never give them up.
Without our dogma/heuristics we get lost. But we need to know how to give up on the ones that are no longer useful.
Most of us will have a hard time recognizing our own dogma.
My claim is that we all have things we believe dogmatically. They for the axioms of our mental models. Take away the dogma, and our entire mental framework risks getting shattered.
If we completely fail to understand this, though, we are at risk of constructing rationalistic (as opposed to rational) frameworks on top, in believe in them absolutely. This can lead to fanatical movements such as Islamic suicide bombers, "Objetivist" capitalists, Extinctin Rebellion or Conquistador Missionaries.
And also all sorts of cargo cults in software development.
I agree but think of it as a scalar. Some mental models are assigned a "conviction" of 1.0 - that could be considered dogma. Some (like the expectation of what tomorrow's weather might be like) may have a "conviction" of 0.3.
Though I think one aspect is missing, namely something describing the emotional loss that will be felt if a dogma has to be rejected.
In fact, the most destructive dogmas may be those where the older has serious subconscious doubt about the validity of the belief, but where their entire identiy may be shattered if they have to give it up.
Questioning such dogma tends to become very strong taboos or potentially heresies worthy of cancelling or even death.
I’d go further, if performance is not part of your initial requirements you’ll never get it.
Performant systems come from architecture and data modeling. Heck in the modern era data load patterns Trump algorithmic efficiency even. Those things are very challenging to back into a codebase.
The feeling I get is that the article tries to make a long list of all possible causes, which dilutes the main reasons why premature optimisation or even optimisation is skipped. It also doesn't try to see the issue from the other side and inform the reader when optimisation is premature and doesn't need to be considered, which makes its argument very one-sided.
One additional point that I think might be missing from this list imo, is the shifting functionality requirements given to the developers which make it less certain that a specific piece of code will be the foundation on which you will build the rest of the logic or whether it will be a leaf-level logic that will be used very infrequently but this comes with other caveats regarding refactoring, etc.
The statement is really a bit of a tautology: what is 'pre-mature' afterall? Well, it's any optimization that is not going to bear fruit for the user.
So the statement might as well be 'optimizations which do not improve the user experience do not improve the user experience'.
I see these sort of statements a lot. 'dont add too much salt', or 'dont drive too fast'. They dont really say much other than 'there is some max point which should not be exceeded'.
I guess my point is that it doesn't really guide us. Of course any 'pre-mature' optimization is not useful. The difficulty is knowing whether it's pre-mature, which can only gained through experience.
I think the reality is that optimizing code is not-fun. Adding features is fun. Writing tests is not-fun. If given the chance, we will find excuses to avoid the not-fun parts of software.
To what is unfortunately a minority, it's the exact opposite. I enjoy optimising existing code more than adding new code.
My gosh man the things I tried to do as a young Eng. myself - some of the tedious things I did to avoid copying strings or this or that! It's hard to see how such things add to 'complexity' in a way that slows down the project.
'Keep it Idiomatic' is a great way to keep things moving and as things become problematic, they can be tweaked.
Obviously in some cases that's not going to work.
I do occasionally hear it used to mean "don't spend the time to optimize until you have measured whether this is your bottleneck and it will actually matter", or "get it working first so that you know this is actually what you even want, otherwise you will end up optimizing things that get cut soon after". Both of which are valid.
Typically after I've made comments on their design very early in the development process about the code being doomed to be very slow, in a manner that cannot be solved by optimizing small parts of the code later (so will require a rewrite).
And I'm not talking about the small wins, that make things 10-50% faster. I'm talking about design mistakes that slow down the code by several orders of magnitude. (Big-O stuff), and that is represented at a point in the design that is so fundamental it's hard to fix without scrapping everything.
Still counts as a win, you can IME take out a cache mechanism easily. If somebody wants to ding you for writing excess code I don't know what to tell you, they are probably a-holes.
Postponing optimization is fine, but if everyone is just postponing it, and nobody is treating it as priority, then it just never gets done. In most software, low performance is effectively an externality - it's unlikely to be the thing that kills your product/business[1], but it's causing real frustration in people and waste real money in the overall economy.
----
[0] - "Work expands so as to fill the time available for its completion"; https://en.wikipedia.org/wiki/Parkinson%27s_law.
[1] - Or at least not obviously so. Performance issues often masquerade under general complaints of bad UX, or the software feeling "off" somehow. Also performance is often a limiting factor in use cases; it can be what enables you to access a whole new market segment, or deny it to you.
Also seen as "don't optimize the idle loop"
And if we're going to proceed by quotation, another one to consider is Linus Torvalds': Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
IMHO, a big thing for junior programmers to unlearn is that one should begin by putting a sloppy version of the program in production, and incrementally improve it thereafter. A little bit of forethought and a bit of attention paid to big-O will do three things: (1) make incremental improvement easier (2) limit the amount of incremental improvement needed, and (3) avoid painting you into a corner wherein significant refactors are needed.
As it turns out, thinking ahead of time is not antithetical to shipping code. Quite the contrary.
What has to be 'unlearned' is that Engineers think they have a clear vision of what they need to do ... but in reality they don't.
This largely has to do with 'unknown unknowns', particularly in requirements.
It's actually a much harder concept to learn/unlearn - because we are trained to design towards specs, towards a kind of perfection, to solve problems in which the conditions are contrived.
Almost nothing in the academic world prepares people for having to build something whereupon the objectives are a bit fuzzy - especially when said objectives are not presented as fuzzy.
It almost requires the 'experience of failure' to grasp that most things that can be incrementally built - should be.
The process of building something, is actually a process of 'discovery'.
I think that the only time 'prepping far ahead' becomes useful, is when Engineers are building on very foundational, learned knowledge derived from a relevant situation, in which case they are actually acting in an iterative fashion, leveraging knowledge from the previous 'situation' - but even then, I would not consider that for optimization, rather, just keeping a lid on arbitrary complexity.
And finally, of course some things are reasonable. You probably are not going to build a high performance codec in Python.
In military strategy there is the quote "Plans Are Worthless, But Planning Is Everything" [1].
I think the same goes for software design. To believe that one can do a complete design of some piece of software, and then implement it precisely as planned, is folly, just as for military plans.
Still, it is imperative to do some deep thinking about the purpose of the software as one starts coding, especially about things like the purpose of the software (and what is NOT the purpose), design and design philosophy and expected challenges (including performance challenges) including ideas about how to find solutions to those challenges.
Also, after starting to code, it's important to revisit these aspects. Is the purpose changing? If it is, is that good or bad? How do we define the new purpose? Are we making changes to the design? Why? Do we need to refine the design concepts? And so on.
Just like for military strategy, this is an art more than a science. We have strong limitations dictated by what is possible, yet it is essential to have an overall idea of what direction we want to move in.
Otherwise we, more likely than not, end up with a useless patchwork.
[1] https://quoteinvestigator.com/2017/11/18/planning/
In my experience it's not well known that requirements are different than specifications and what those differences are. Knowing the difference makes understanding why planning is useful but "plans" are useless.
Requirements state the desired properties of a system that affect our world. They are written from the perspective of humans interacting with a desired system to achieve some goal. It's useful to know that we need a system that does X so that we can Y. We need to ensure that Y eventually happens when the user initiates X. If the system cannot succeed at Y within Z minutes we need to notify the user so that they can initiate procedure J and halt the process manually.
Requirements don't have to tell us how to implement such a system. They're more like the end state. It's the job of specifications to tell us, technically, how such a system operates.
We, the developers building the system, don't have to write the full specifications up front. That's a bit of a mistake. Because the gap, as the GP points out, is quite large from the starting point and the end result. It's quite normal to start with the bare minimum specification and iterate towards an ideal one. As you learn about the problem space we can build more certainty about how such a system can be implemented. We can update the specifications, verify the code implements the specification, and hopefully in the end arrive at a system that fulfills our original requirements.
It's rare that one can write the full, complete specification and leave it up to the developers to implement it.
This actually makes the analogy a bit closer to startupland.
The #1 factor in terms of predicting project outcomes is the 'quality of the people', so 'training' speaks to that.
This is why I believe good early stage startups are so often focused on teams, not business models, because the later will change as the team orients dynamically towards 'something that works' and the analogy is right there for software as well.
Embarrassingly, I'm working on a project now that would have been done 4x quicker were I to have known at the start what I do now! But that's how it works.
This is quite a bit different than 'premature optimization' however ... but maybe it's related in terms of idioms and architecture, which are derived from experience.
A good dev/architect can probably identify the 'roughly appropriate architecture' very quickly for something, which is the expression of a lot of built-in knowledge, and might encapsulate a lot of things. Aka: 'yeah we don't need protobuff for that or anything fancy, it's not worth it, just use json over http and you're good' etc.
Thanks yourself :)
> , but one small thing - in the military it's really not 'planning' so much as it is 'training' (or 'preparing').
I think this depends a lot of the rank as well as the type of conflict. At low ranks and in a strict C&C doctrine, I suppose drill, practice and discipline play a huge role At higher ranks or higher levels of autonomy, war becomes way more of a mind game, while motivation may be an ok replacement for discipline.
When I used the term "Military Strategy", I meant primarily the operational level and above, or basically from a division commander (Mj.Gen.) and up, as well as the general staff.
At the tactical level, war becomes more of a science or craft, governed by doctrine and training. Higher ranks, though, are more likely to encounter the novel and unexpected, especially if facing an enemy of approximately equal strength.
> The #1 factor in terms of predicting project outcomes is the 'quality of the people', so 'training' speaks to that.
Training is important, for sure, and if you are in a position to only hire the very best, so much the better.
But technical leader will still need to figure a lot of stuff out about emerging trends and technologies as well as find litterature and other writings (or videos) for more established domains (that are new for their company). For these roles, there are a lot of ways to screw up, whether through stagnating or going all cargo cult based on a trend they don't fully understand.
What has been much, much more worrisome in my practice, is ‘unknown knowns’ which is something developers should know but do not. Poor design and engineering skills, low level of professionalism is what keeps plaguing software industry. (Remarkably, most other industries, those that have to deal with material things, do not suffer from this - not on this scale anyway.)
Both of the quotes in your comment resonate with me, in the sense that they can avoid unnecessary refactors. But IMO the big-O/performance part should really mostly just be done on a by-need basis (i.e. you've identified your bottleneck and determined its worth the time to improve it). I've seen way too much unreadable code because "otherwise it wouldn't be performant" in places where performance doesn't matter, and similarly I've seen people sacrifice a clean modular interface for the sake of performance.
Sure thinking ahead of time makes sense, as does choosing a design which puts flexibility in the right places (i.e. gives you room to adapt to what you learn in the future which is currently uncertain). But that same uncertainty also means you're probably not going to get the design fully right now, so don't overthink it.
I agree
>(i.e. you've identified your bottleneck and determined its worth the time to improve it).
For this, though, it is often possible to identify this BEFORE writing any code. As opposed to refactoring an inner loop, Big-O challenges may often stem from (at least in my experience) the top level design of the code, and changing it may require rewriting almost everything.
This is particularly true in a microservices world, where the problem may often be how you split a computation task up into services in the first place.
I was in a design discussion this morning for some new functionality which requires hitting a pay-for API; and much of the design was based on minimising how many calls we'll make. A naive polling approach would require O(t×e×u) requests, where t is time, e is the number of the events we care about and u is the number of users affected by those events. The design we came up with uses a separate data source to identify when those events occur, which removes the factor of t. We also came up with some de-duplication logic which reduces the constants.
I think I've actually seen performance decreases by people trying to decrease BigO. BigO only starts to really dominate time when N reaches higher levels. At lower levels of N your performance on your constant operations matter more, and by implementing more complex algorithms actually decreased real time performance. Obviously a lot of nuance in this. But first you need to measure.
- inefficient design around data dependencies and their identification
- redundant work performed around said dependencies
- excessive reliance on APIs for uses they’re not intended, which frequently causes…
- …excessive VM deoptimizations and excessive GC
Granted this is one application and I certainly don’t think it’s representative of anything. But it’s definitely a counterpoint to inefficient concurrency (where this app generally performs quite well).
As for timeouts, IO and deadlocks, those problems can also be a case of poor up front optimization (before codigng).
The things you list are the low-hanging fruit, after which most engineering teams throw their arms up and say "I guess we've gone as far as we can on this!"
Consider a piece of code that grabs a set of records and then loops over them to get an association for each one. I've seen this kind of code in code reviews from juniors. its a good starting point where I start showing them how to rewrite their sql call to get the associated record via join so it goes from O(n) network calls to O(1)
Bad code is laid down at day 1 and everything past that is just worsening it even if it appears to be a positive refactor. Torch the canvas and embrace the cycle.
Not that it has to be preplanned, per se, as the absence of planning before day 0 still becomes plans on the day IME.
Requirements and understanding change, and so code should change as well, and this happens with greater frequency earlier in a project. But code that clearly expresses its intention makes that much easier -- someone reading it (maybe even the original author!) can better see "does this code actually do, what it thinks it does, does it do what we need it to do".
Optimizing for speed or memory footprint might or might not be premature optimization. It's not always easy to tell. But optimizing for _clarity_ is never a premature optimization.
What once was Optimization gets called Information Architecture, Capacity Planning, and so on, and all of these things should be done as soon in the project as possible.
> IMHO, a big thing for junior programmers to unlearn is that one should begin by putting a sloppy version of the program in production
This is the closest we've gotten to a Just Do It mantra but we have a lot of space for improvement. An instructor can't help you get better at public speaking if you won't speak at all until you can speak well. However once you can speak well you shouldn't rush important speeches because you can't walk some things back once they're out there.
We will always have a steady stream of brand new developers who need simple rules of thumb, but some senior developers never grow out of it. What I see is a lot of these aphorisms being used as a way to censor others who might be suggesting work in the "hard but valuable" quadrant of cost vs value.
At some point in my career I got so fed up with coworkers trying to go against business needs (customers are complaining about responsiveness) by shaming coworkers into not touching performance and essentially running a work slowdown, that I began identifying types of code refactoring that improve clarity and speed. Sometimes what confuses the developer confuses the compiler. Particularly in the realm of strength reduction.
Think about what the pros and cons are, what the scope is, budget, and... decide what to do. I've never come across one of these articles that doesn't boil down to "think about it".
That's not to trivialize it, because of course it can take years to hone your development judgement powers, but essentially such articles are only useful so far as they tend to contain a long-form list of commonly encountered thinking points.
More specific experience gives you more detailed items on your list. Level 0 of the performance considerations (relevant to every vertical) is maybe whether you can just throw more hardware at it. In my little vertical (finance) you might think about what data structure to use based on what instrument type you're building a system for.
Translation: Don't suck the entire DB into RAM to read a single value.
This is the key thing. I can hardly think of single application where my number one feature request isn’t “make it faster”. Rarely am I interested in a “fresh new look” or “more buttons”. If you can make some task take 5 seconds instead of 10, that is a win for everyone.
Visual Studio Code is actually a good example of "performant enough" software; it's famous (appealing) for its extensibility, but certainly not performance. Then there are cases like Slack or GNOME 3, which were adopted even with lackluster performance. Go figure :|
There are fields like that. Then there are fields that seem to be like that, but where it's just the person making the purchase decision that cares about "features-features-featues", and actual users don't use most of them and would instead prefer faster software with sane UX.
I've definitely seen more of the latter than the former in my life.
> Then there are cases like Slack or GNOME 3, which were adopted even with lackluster performance. Go figure :|
Nothing to figure out here, it's about who is making the choice, and what choices are available on the market. Slack case is the very common situation of the choice being made by few people (not necessarily best suited for making that choice) for the whole organization / community, and heavily influenced by all the marketing spend and dirty tactics Slack the company did. GNOME 3 is the case of a supplier-driven choice: i.e. some distros defaulted to it for whatever reasons, and people mostly had to accept it, because WM is one but many qualities you care about in a distro, and alone not enough to make you choose a different one.
Both types are common situations on the market, and not at all limited to software. To again name them, in short:
1. People making the choice are not the end-users of the product/service.
2. Choice is made by the supplier, and the market doesn't provide an equivalent alternative that made a different choice.
I think it can also be management layer driven. Making something a little bit faster is so much less visible than adding a new, observable / obvious feature.
If we're taking Pareto principle into account, I can say that we can do 20% of optimization to get 80% of the benefit. The more experienced you are, the easier that first 20% is.
Premature optimisation is not an excuse to avoid thinking about runtime characteristics either at design or implementation time.
Premature optimisation is the junior engineer using some cargo-cultish ideas to attempt to shave a couple of instructions off a loop which a) is unlikely to be any sort of bottlneck and b) is going to be picked up by the compiler anyway if it's that simple.
It's spending days of effort making some piece of code faster when in practice that piece of code is neither executed often nor on any critical path.
"The Fallacy of Premature Optimisation" seems to me to be an article which takes the worst behaviours of people who don't believe in optimisation at all, and uses them to dismiss the entire idea that effort could be misapplied in dead-end microoptimisations that don't achieve anything.
Some of them will then happily do what you correctly label "Premature optimization" in the microcode, but proceed to make big mistakes in the overall design that leads to several orders of magnitude slowdowns in the general design.
I believe the problem is in the training. As junior devs, they may have been scolded for premature optimizations of microcode, but worked within a well thought-out design at the macro level. Then they advance in ranks, and have to do the design part as well, but they never learned to think about the performance at that level, something that benefits a lot from some contemplation up front.
When you multiply by really big numbers, the problem becomes very apparent. A wasted millisecond in a function doesn't seem like much until you call it a million times.
Our requirement for a traffic control system might be primarily concerned with the safety of motorists and pedestrians crossing an intersection. However it's generally implied that this system should eventually allow some cars through and some pedestrians through. And that nobody is left waiting too long!
Such a system wouldn't be terribly useful if pedestrians never get a chance to cross because the light never changes.
It's not premature optimization to design your system with resource constraints in mind. It's smart engineering! A user shouldn't have input lag to scroll a document. The game engine doesn't need to render an infinite number of entities. An HTTP resource shouldn't be returning every record in the database. If you keep these constraints in mind in the design phase you generally end up with a well performing application.
Ignoring them is what I've heard called, "premature pessimization," and is a popular source of performance problems in mature applications.
The important thing to remember is that the earlier in development something is fixed, the cheaper it gets. The longer a problem persists throughout the process, the more expensive it will be to fix.
And "more" here means "exponentially more". Because at the time of design or discussing with the client I may be able to fix it in a second by just choosing a different option. Once it gets to production it might be either impossible or costs millions and months of development.
The job of the person who designs it is to identify which decisions will be hard to undo later if they are found to be wrong and focus on making sure these are done correctly.
I'm not familiar with that maxim but it sounds very waterfally. That's a maxim agile developers would have rejected two decades ago. (In fairness this article is from 2009 when agile was still relatively new.)
1. Make it run.
2. Make it right (Make it correct).
3. Make it fast.
...with the observation that if you complete step 2 then it will often be pretty fast already due to muddy logic having fallen away.
The Fallacy of Premature Optimization (2009) - https://news.ycombinator.com/item?id=21912667 - Dec 2019 (103 comments)
The Fallacy of Premature Optimization (2009) - https://news.ycombinator.com/item?id=13505721 - Jan 2017 (59 comments)
The Fallacy of Premature Optimization - https://news.ycombinator.com/item?id=10954554 - Jan 2016 (1 comment)
The fallacy of premature optimization - https://news.ycombinator.com/item?id=1273288 - April 2010 (2 comments)
The Fallacy of Premature Optimization (sometimes it's good) - https://news.ycombinator.com/item?id=227262 - June 2008 (4 comments)
Simplicity + Readability >>> performance
If I can look at a method and think of obvious speedups then it was a good method because I could easily read it and be confident about what it does.
Loads of performant code is really hard to read because it's full of magic that speeds things up. When you try to modify this code you're never sure if you need to keep the "performance" especially if there's no performance test.
I've also done speedups of legacy software. What you find is that most of it is "basically instant" and one method takes 5min. You rewrite that method to be fast, add a performance test to keep it fast, and a big comment explaining exactly why it needs to be fast and how it works.
That's if you go down to the lowest levels of optimization. The kind of things you see in compression, hashing, sorting, codecs,... Really hardcore optimization because otherwise, you wouldn't be able to watch that video at without turning your device into an heater, if at all.
But in more reasonable cases, simpler code often turns out faster. The less you do, the faster it runs, which is kind of obvious. Readable code is also a good thing for performance, if is easier to see and fix inefficiencies if the code is readable. A good part of performance problems are simply bugs: memory leaks, operations done several times for no reason, etc... and many others are fixable with no loss in readability, for example by using the proper data structure.
As for the last part, use a profiler, there is no point optimizing without a profiler. If you don't have a profiler, then good luck, it is doable, like investigating a crash without a stack trace is doable, but it is horrible.
One thing I do no matter what: always use the algorithm with the best big-O complexity available, unless you have a good reason not to. You may think you don't have enough data for that O(N^2) or worse to matter, but it can change, in fact, this is the most common type of change, and once you get in the zone, it can get really slow really fast, lookup "accidentally quadratic".
My resistance to the term "Premature optimization" is generally not about optimizing single methods, but usually either about optimizing how to split an application up to modules (services, classes methods) or about what technology to use for each component.
For instance, if you have an "inner loop" or basic computation unit that requires you to do several REST requests to other services (serially, with little room for parallelism) where it would have been possible to maintaina few hashmaps locally in your service with the same data, you may easily take processing time from 1 hour to 1 week for some computation task.
With some experience, you should be perfectly capable of undersanding this way before defining how to split your workloads up into microservices.