Big-O matters, but it's often memory that's killing your performance
cocoaphony.micro.blog
cocoaphony.micro.blog
As with everything involving Big O notation: it's a first order approximation. (Literally so, as that's exactly what Big O means, summarize things to the first order in the presumed polynomial equation that describes it time or space [!] complexity.) The exact same analysis process is useful for more than just "number of algorithm steps" (time) and can be useful in examining algorithm space requirements ("number of memory units", say). Also, it's still an approximation to start an analysis and that's rarely where you should stop in your analysis in real life.
1) Stupid human mistakes. IE: improper configuration, checked in the wrong file, no testing, etc.
2) Resource constraints, normally because someone was being cheap and thought they could get away with less memory / cpu / network / database / etc than was needed.
3) Architecture. Often the real root cause was that either a system had outgrown its original design, or there really wasn't much of a design in the first place. These were the worst, because they were not easy [fast | cheap] to solve.
To the article's point - I don't think I ever saw a single instance of "wrong algorithm" IE: a case where "Big O" was at fault. Did see interesting memory situations, but even those were relatively uncommon compared to "Why are you doing this in js rather than in your db?" or some variation on those other themes.
In all those cases the "Big O" of the chosen algorithms is never directly "at fault", it's an approximation tool so of course it will rarely be a "smoking gun" in the real world, but it can be a very common hidden "root cause" that's easy to overlook in problem "post mortems", often: Had the developer at the time a thing was written been better aware of the "Big O" complexity of their chosen algorithms they might have found potentially better trade-offs for resource utilization given resource constraints or for architecture given expected long term design.
I know that's a lens I apply a lot when doing performance work and trying to find "true" root causes, thought exercises along the lines of: What was the intended "Big O" of this algorithm when originally written? What in practice violates that intention? Where are hidden "Big O" costs that a developer might have missed because it seemed ancillary to their core algorithm they were working?
(In .NET, for a concrete example of applying "Big O" to performance fixing, I can go to some length on "ToList Considered Harmful" and how it's a common accidental O(n)/O(n^2)/O(n^m) sink, in both time and especially space [memory usage], that is non-obvious because it is so easy to write .ToList() everywhere and so common in many codebases/code-styles to find it mindlessly everywhere and a lot of APIs wastefully use IList<T> or ICollection<T> rather than IEnumerable<T> or IQueryable<T> as their API boundary and a lot of developers use "ToList Debugging" as a crutch and yet ToList doesn't look like it intentionally means to be a part of the algorithmic complexity of the actual business logic it interferes with.)
Reminds me of Kirk Pepperdine's "The Trouble with Memory" talk: https://www.infoq.com/presentations/jvm-60-memory/
Quotes:
"Reduced allocation rates from 1.8 gigabytes per second to zero. (...) TPS jumped from 400,000 to 25 million."
"about 70% of all the applications I run into are bottlenecked by some sort of memory issue (...) not only are people bottlenecked on this issue, they don't actually realize it. They don't even see it. It's not even visible to them."
Then he goes on to enumerate some popular Java frameworks, and says: "If you use any of these, then you're very likely in the 70% of people that are suffering from memory issues."
Algorithm I see is very simple, nothing could brake there, but author don't share communication routines, and here is core of problem.
Few years ago I have worked on project, where one thread sends huge amounts of data to other, using standard CriticalSection routines to synchronize threads.
They are simple to use, but even with hardware support are extremely slow - on 3Hgz class cpu give about million transactions per second.
I was too lazy to finish that project, but it's very clear for me, that I just need to rewrite messaging part, to send not one string, but batch of strings, so million transactions per second will be for example, 100 millions strings, or billion strings.
Same thing I see here - accept inter-thread communication is laggy, and rewrite code, so it will send larger packets, so there will be less transactions, and you will get easy performance gain.
I assume at the beginning that the unit of work is almost certainly so small that you can do many units of work in the time it takes to move a unit of work from one thread to another. Usually the other people are obstinate and insist on implementing something that is slower than serial execution, but they do come around unless they give up entirely when their attempt at a parallel speedup really causes a slowdown.