16 ms·
100%, I’ve done tonnes of (backend) performance optimization, profiling, etc. on higher level applications, and the perf bottlenecks have never been any of the
by yashap 4y ago
100%, I’ve done tonnes of (backend) performance optimization, profiling, etc. on higher level applications, and the perf bottlenecks have never been any of the things discussed in this article. It’s normally things like:
- Slow DB queries
- Lack of concurrency/parallelism
- Lack of caching/memoization for some expensive thing that could be cached
- Excessive serialization/deserialization (things like ORMs that create massive in memory objects)
- GC tuning/not enough memory
- Programmer doing something dumb, like using an array when they should be using a set (and then doing a huge number of membership checks)
With that being said, I have worked on the odd performance optimization where we had to get quite low level. For example, when working on vehicle routing problems, they’re super computationally heavy, need to be optimized like crazy and the hot spots can indeed involve pretty low level optimizations. But it’s been rare in the work I’ve done.
This article is probably meaningful for people who work on databases, games, OSes, etc., but for most devs/apps these tips will yield zero noticeable performance improvements. Just write code in a way you find clean/maintainable/readable, and when you have perf issues, profile them and ship the appropriate fix.
- jimbob45 4y agoWell said. The aphorism "Premature optimization is the root of all evil" is meant to mean "Build it right first, then optimize only what needs to be optimized". There's really no need to start cooking spaghetti right off the bat. Clean code with some performance tweaks will be more maintainable in the long run without sacrificing performance.
- TeMPOraL 4y agoCasey's implied point is that clean code is already sacrificing performance from the start. And of course real life tells us that those "performance tweaks" will never happen. There is this popular wisdom that security must be designed for from the start, and cannot be just added after the fact. Performance is like that too, except worse, because you actually can add security after the fact - worst-case, you treat the entire system as untrustworthy and wrap a layer of security around it. You can't do that with performance - there is no way to sandbox your app so it goes faster. You can only profile things then rip out the slow parts and replace them with fast ones - how easy that is depends on the architecture and approach you adopt early in the project.
- loup-vaillant 4y ago> there is no way to sandbox your app so it goes faster. In a number of cases you actually can. Casey demonstrated it in his Refterm lectures, it's caching. You still call the slow thing, but at least you don't call it as often because you have that layer of caching to partially insulate you from its poor performance. Good luck if you have to deal with cache invalidation, though.
- TeMPOraL 4y agoFair enough. I'll concede on saying that performance and security are alike - you can add some of either after the fact, but you're better off thinking about both from the start. > Good luck if you have to deal with cache invalidation, though. Ain't it the truth. Adding a cache is easy. Understanding the implications of doing it is harder.
- Turskarama 4y agoThis is not my experience at all, at my org we always write it the simple way first, and if it needs more performance after the fact then we always add the performance. User friendliness always comes first. This includes not waiting 20 seconds for a db query that could be rewritten to happen in 1 second, but it also means not waiting a month for a bugfix which could happen in a day with maintainable code.
- TeMPOraL 4y agoThat's a nice approach, and I envy you the environment you work in. In my experience so far, it's typically the case that the 20 second DB query will annoy people for months or years - it won't get solved until enough people raise enough of a stink that someone finally prioritizes it. A large customer suddenly starting to make vague hints about bad performance is sometimes (but not always) helpful. Some may say that "annoying, but not enough to make a stink about it" means it's fine to not optimize it. But I found that people can suffer a lot, and it doesn't mean it's harmless. People will adjust their workflows to minimize the frustration. When some of your "victims" are in-house users, the "not important enough" performance issue may be silently but continuously losing company money.
- p1necone 4y agoCasey Muratori knows a lot about optimizing performance in game engines. He then assumes that all other software must be slow because of the exact same problems. I think the core problem here is that he assumes that everything is inside a tight loop, because in a game engine that's rendering 60+ times a second (and probably running physics etc at a higher rate than that) that's almost always true. Also the fact that his example of what "everyone" supposedly calls "clean code" looks like some contrived textbook example from 20 years ago strains his credibility. Edit: come to think of it, the only person I know of who actually uses the phrase "clean code" as if it's some kind of concrete thing with actual rules is Uncle Bob. Is Casey assuming the entire commercial software industry === Uncle Bob? It's like he talked to one enterprise java dev like 10 years ago and based his opinion of the entire industry on them.
- magicalhippo 4y agoThen again, thing's aren't in the critical path until suddenly they are. Regardless of scenario I will never willingly do a O(n^2) sort when writing new code. Just in case those 10 items suddenly turn to 10000 one day.
- joaonmatos 4y agoAre you even manually implementing sorts frequently? Even languages that are notorious for having tiny libraries, like C and JS, have built-in sorts.
- mattgreenrocks 4y agoIt’s less about accidentally writing n^2 sorts and more about not accidentally creating n^2 algorithms.
- whstl 4y agoNot sorting, but very simple algorithms that can't afford having O(n^2) performance? That's common even in CRUD apps.
- dagw 4y ago
- slaymaker1907 4y agoORMs and slow DB queries kind of go hand in hand. Also, you'd be surprised at how efficient arrays are for membership checks so long as the number of items is even moderately small (as a rough rule of thumb, the only thing that really matters with these kinds of checks is how many cache lines are involved).
- fvdessen 4y agoIf you program using design patterns that are 10x slower, your application end up 10x slower, even after you've optimised the hot spots away, and the profiler will not give you any idea that it could be still 10x faster.
- rileyphone 4y agoNot the case if your program is spending most of its time waiting, which is typical these days.
- whstl 4y agoNot necessarily in the general case. If the program is a single user, locally ran program, then sure. If it's some sort of backend or distributed service, this is just wasted performance that can be used to serve more users. Virtually every distributed service built today is able to take advantage of this. With a non-pessimal design, not only you are able to pay less money on servers in the long term, you're also able to delay complex scaling strategies. Scaling also costs money. Not to mention that building something with this kind of overhead also means that a lot developer time was spent in the first place, which is still expensive in our industry.
- Zvez 4y agousing 'unclean' code practice will increase development costs. And more importantly - maintainability of such code. >Virtually every distributed service built today is able to take advantage of this most of built today services can take much more advantage in using better system design practices.
- deleted 4y ago[deleted]
- badsectoracula 4y agoIf your profiler doesn't show any hotspots (which is incredibly rare in practice) and your program is still slow, it means you can simply pick any of whatever functions/methods show up near the top to optimize. If your program isn't slow then you don't need to bother making it any faster. Even Michael Abrash, who specializes in code optimization, once explicitly wrote in his graphics programming black book that "The objective (not always attained) in creating high-performance software is to make the software able to carry out its appointed tasks so rapidly that it responds instantaneously, as far as the user is concerned. In other words, high-performance code should ideally run so fast that any further improvement in the code would be pointless [..] Notice that the above definition most emphatically does not say anything about making the software as fast as possible".[0] [0] https://www.jagregory.com/abrash-black-book/#understanding-high-performance https://www.jagregory.com/abrash-black-book/#understanding-h...
- hsn915 4y agoAll of these are instances of doing something _wasteful_, which is the #1 issue he mentions in the list of things that cause performance degradation. Now, your argument seems to be: in the real world, there's so much waste, that virtual function calls pale in comparison. This does not debunk his main point, which seems to me at least the following: all things being equal, writing code with virtual functions that do a tiny amount of work and "hiding implementation details" makes performance worse, sometimes by an order of magnitude. Now, there maybe situations where you _have_ to use virtual functions, because you are writing a library for other people to use, and you can't dictate ahead of time how they will use it. This again does not invalidate the point. You need to be _aware_ of the performance implications of this, and mitigate it. He said the following in the comment section on the article: > Try to make it so that you do very rare virtual function calls, behind which you do a _large_ amount of work, rather than the "clean" code way of using lots of little function calls.
- Zvez 4y ago>all things being equal, writing code with virtual functions that do a tiny amount of work and "hiding implementation details" makes performance worse, sometimes by an order of magnitude but all things are not equal. You can spend a lot of time improving performance of you function calls and get virtually nothing out of it. Because if you optimize something that takes 0.01% of overall execution time, 'order of magnitude' performance gain is still negligible. Also articles like this usually fail to mention code maintenance cost. For example by reducing usage of virtual calls you can make your code unmaintainable/expandable and suddenly every new change will cost you 2x more in development time. That's why in the real world most of the time you choose clean code and you use optimized nonclean code only on places where you need it. If you look at any lets say web framework internals, you will find a lot of non-clean code, which makes framework faster. But an interface will be done in clean fashion and most of user of the framework will enjoy clean code without need to care about unclean internals.
- hsn915 4y agoThis article is not aimed at people who are working in a codebase where everything is super terrible. It's written for performance aware programming. One of the aspects of performance awareness is awareness of how virtual functions and tiny functions affect performance negatively. > Also articles like this usually fail to mention code maintenance cost. For example by reducing usage of virtual calls you can make your code unmaintainable/expandable and suddenly every new change will cost you 2x more in development time. This never actually happens in real life. I've never seen a codebase that is written with "clean code" principles in mind that is also maintainable and easy to develop on top of.
- anitil 4y agoHow did you get in to that work? I love this sort of optimization but not sure how to get people to pay me to do it full time.
- jbverschoor 4y agoWell that's exactly the difference between systems programming and application programming. Don't forget virtual machines and interpreters.
- e28eta 4y agoAuthor could use a little more memoization in his example, but I suspect that breaks some of the simplicity of his argument. If shape Area is computed often enough that you care about inlining the calculation, why not compute & store it every time the height / width change. That’d be easy enough in an architecture based on information hiding, and might illustrate a legitimate engineering trade-off between those architectural choices.
- cratermoon 4y agoRight? His Area() function does the calculation from the scratch every call. Either a) make the Shape immutable and calculate area once, at create time, or have the mutator functions recompute the area when they are called. At that point Area() just returns an f32, and the compiler can do all kinds of optimizations.
- cratermoon 4y agoSpeaking of mutators, his example depends the code executing in a single thread. What happens if calls to mutate the shape and calls to calculate the area are interleaved?
- incrudible 4y agoYou are completely changing the problem. Remember, this is an example, the code does the same thing in either implementation.
- cratermoon 4y ago> completely changing the problem Indeed. That's the point. Why let someone with an axe to grind define the problem in a way they can solve with their axe? The author is using optimization as the frame for rejecting a particular way of working, I'm pointing out that the definition is set up to make the solution work. I don't concede the terms of the debate before it even begins.
- munk-a 4y agoI'd actually say this article is generally unhelpful - it's good to be aware but as someone who works on sorting out performance critical things I want the code to be as clean as humanly possible going in. Whether you write clean or dirty code if you're a junior developer you're probably not going to write performant code and even senior devs may be able to sniff what might be a bottleneck in advance but most of us have learned to avoid premature optimization like the plague. Maintainability and cleanliness is the best virtue code can have. If you have extremely clearly written code that has performance issues I can swoop in with analysis tools figure out where the pain point is and refactor it out. Sometimes this is a real headache[1] sometimes not - what I can guarantee is that if the code is "dirty" it's going to be a headache and it'll take more time. I'd personally take issue with this article over the polymorphism claim though - polymorphism is a tool but it isn't the be-all and end-all tool. A lot of your data can live as structs/blobs in memory with tight internal type definition but without any OO principals. Personally I am a huge fan of functional programming (but not pure functional programming) so objects that I use are relatively few and far between and exist to fulfill a very specific purpose. I've had two occasions in working when I needed to break out an asm block - the compiler was being a thick headed dummy and this code needed to receive incoming signals without exception or delay - but once that critical section was passed? Back to high level programming and statements favoring expressiveness over raw bare metal performance. If you want an interesting experience talk to your closest non-technical manager type - be that a product team manager or the company owner - and ask them if they'd prefer if you focus on reducing how long your product takes to execute by 20% over the next five years or if they'd prefer you to lower the growth of the developer labor budget by 20% for the next five years by focusing on maintainability over performance. With the exception of extremely niche cases maintainability is always the golden standard. 1. For instance, I've dealt with OOM issues that have required transforming all logic on a query result to be lazily evaluated on a data stream after main execution finishes - like the logic goes up and down the stack and only then begins processing results. In this particular case the problem was rather easy to deal with because we essentially swapped out the actual value passing on each layer for a lazy result set being passed around - because the code was clean. Sometimes you'll definitely need to massively re-engineer things though.
- whstl 4y ago
- osigurdson 4y ago>> Lack of concurrency/parallelism Definitely get the single-threaded house in order before attempting to speed up by running in parallel.
- yxhuvud 4y agoDepends. For example, if the slowness comes from sequentially emitting a lot of http requests, a lot of performance can be gotten from doing it concurrently.
- noobermin 4y agoTo be fair to OP, does anything about your current paradigm even allow you to evaluate his claims? Is your code even in a form that you can for example remove polymorphism and use simple arrays of data you want to work over? You can of course only optimize what you are looking to optimize. I am not surprised (honestly) that some engineers do not realize they are in fact primed for the kind of things they will find based on what they are looking by the mere choice of where they want to look.
- DeathArrow 4y agoMaybe for low user count is valid. We run a large multitenant, microservice based application and the physical machines where the Kubernetes pods reside have their CPUs at 90%. The application makes such a large use of "clean coding", "design patterns", SOLID that would make Uncle Bob proud. We would have been better without using so much abstractions on top of abstractions.
- flumpcakes 4y agoThis is my experience. As I have said elsewhere, perhaps I am unlucky and work with "bad" developers, but everything being built for the future at $COMPANY is over complicated and has a multitude of abstractions. Honestly, for most software, a well performant "monolith" is probably enough. We had one service that had a single-thread bottleneck that none of the developers could configure out - the solution was to spin up 30x more virtual machines to run instances of this app to meet average production demand.
- rkuska 4y agoAnd let's not forget that majority of CPU cycles is spent on encoding/decoding json requests.
- thermin 4y agoStill unvectorized in Java
- citrin_ru 4y agoWrite slow code now, profile and optimize later is how we got all slow software because second step - optimization practically never happens in my experience. Along with the heuristic that hardware and electricity are cheap, developers are expensive. That's probably why managers in my experience almost never ask developers to optimize slow code - they believe it would be cheaper to use more hardware if this fixes the problem (in some areas like HFT or GamDev more hardware is not the answer so optimization do happen). In rare cases I've seen optimization being done initiative always come from IC (who knew that the code could work fast / use less resources). So nowadays writing code I assume that it never will be optimized later and try to do less dump stuff from the beginning.
- moonchrome 4y agoIf it doesn't happen then it doesn't matter.
- citrin_ru 4y agoIt doesn't matter in a sense that most companies continue to be profitable even paying significantly more for hardware / clouds than they potentially can. But it is sad to see nevertheless. Also it increases CO² emissions.
- radicalbyte 4y agoThe heuristic is one of those for the mid-experience developer. That's the point where you spend two weeks re-implementing Set<T> to save 1 cycle. Or over abstract. Lots of developers never get past that point. Experienced devs - such as yourself - generally know how to write reasonably fast code that is also clean (or easy to extend) first time. In my opinion we should be more explicit about this in the heuristics.
- Zvez 4y ago>Write slow code now, profile and optimize later is how we got all slow software because second step We got slow software because we were ok to get slow software. If being fast is not in requirements it means it doesn't matter (for whoever is responsible for defining priorities). Places where performance matters it is never sacrificed.
- hallqv 4y agoGreat reply, thanks for sharing your experiences!
- Dave3of5 4y ago> Slow DB queries What I've seen is slow queries but a bigger problem is actually too many queries. It's easy to do especially when using an ORM. It mostly happens on change when you want to add something to an existing query the changer just add their new query and slop it into a loop, boom performance is gone.
- nijave 4y agoIn a business context, it usually happens that hardware is cheaper than software (licensing) which is cheaper than engineering labor. Tack on the opportunity cost of delaying business advances/features and it's usually cheaper to just throw hardware at it. There's a tipping point where you have so much hardware there's big savings with optimization. Things like Postgres and the Linux kernel have a lot of optimization put into them and there's an insane amount of hardware out running that code. That said, slow software sucks.
- LorenPechtel 4y agoOne I found with profiling--when writing the code n was quite small. Many database operations simply iterated over an array to decide where to store an item. The time spent dealing with the data was a tiny fraction of the database round trip time, there simply was no reason to get fancy. Over the years they've grown and one bin showed up where jobs would sit around for weeks--for that case n went from rarely having a screenful of lines to a few thousand--oops, now more than 2/3 of the time was spent in those searches. (And I have a sneaking suspicion that a good portion of the remaining time comes from using field names to retrieve values. The profiler doesn't separate that out, though, because it's not my code.)
- gofreddygo 4y agoSome more indirect reasons - shit UX "ideas" that trigger 10 new API calls to show dialogs and popups. - Logging libs under pressure - overemphasis on testability