Power of small optimizations
maksimkita.com
maksimkita.com
Don't underestimate accumulation of small improvements.
What the SQLite team did was systematically measure the performance of their system, identify potential bottlenecks/inefficiencies, create and measure improvements to these bottlenecks and choose the better option.
This is what we call optimization. Premature optimization is when you have no idea how your system really performs, but spend time trying to make it "better" without having a way to determine whether your change made any difference.
I'd refer to what you're describing as messing around.
To me, the core of the issue is knowing what you're doing. If you know what you're doing then it's generally not premature. You have a reason for what you're doing. You're sure that you're working on the right part of the code and you are properly measuring the difference in a meaningful way.
> Furthermore, there is literally no way to tell whether your program will ever actually terminate without actually executing it. And there are many, many, many, ways to write a program which make it “slow” to execute. “Slow” being… like really slow.
Nitpick: there are some languages where you can tell whether your program will terminate without running it. There are even languages where you can tell whether your program will be slow.
And there are even more specialised languages where any program will be 'fast'.
Obviously, these languages aren't Turing complete. It's an interesting exercise to explore the design space to find languages that are both useful and avoid being Turing complete.
Agda is an example of a language that allows you to run approximately any practical computation, but it's not Turing complete. Oversimplified: in Agda a program is only considered valid by the compiler, if it comes with a computer-checkable proof of halting on all inputs.
Programs with a fixed amount of execution time always terminate. That’s why we add timeouts to executions, because they’re a solution to the halting problem, which guarantees execution flow of our machines always returns at some point.
For example, these (obviously inefficient) functions always terminate for all values of x, since both terminate given an input of 0, and 0 is the minimum possible input to these functions.
bool even(unsigned int x)
{
return (n == 0) ? true : odd(n - 1);
}
bool odd(unsigned int x)
{
return (n == 0) ? false : even(n - 1);
}While statically verifying your snippet is possible, it is also significantly more difficult to do than enforcing rules like "all loops must have an upper limit". I also think that you'd need dependent types to make this approach viable. Your simple example is IMHO rather an exception, just having "unsigned int" parameters / return types would mostly not suffice to statically verify that a recursive method will finish or not. (plus things like global state etc.)
But it's not the only way to get a total language (that always halts), contrary to what your original comment claims.
Neither language is Turing complete.
Interestingly, a Turing complete language without non-halting programs could exist conceptually (rather, many do); it's just impossible to know it has that property (hard to use in practice if you don't know it has the guarantees you'd like), and it can't be finitely describable (impossible to implement a compiler for it which doesn't sometimes "succeed" at compiling invalid programs).
Perhaps, for the "scientist" writing that initial code, their algorithm was also already the best that could be done because they just don't know much about algorithms and techniques to make them faster?! But once you learn about things like divide-and-conquer, work avoidance etc. it becomes pretty much self-evident when your algorithm sucks... it's really not that hard - definitely not for people whose job is stuff like climate science who have a very good understanding of maths.
Maybe by teaching scientists the basic "smart" algorithms and just a little about algorithm analysis (Big O notation), massive computation improvements could be had.
But for the scientists that wrote the original code, maybe not. Maybe they think of this sort of thing as drudge work, something that doesn't really hold their interest. Their fun is in designing the mathematical concept, and turning it into code is just a chore.
So yeah, we could teach scientists. But even better would be if we could provide scientists with tools that are just naturally fast when expressing problems on their own terms.
You have to optimize the system, as a whole.
It's not about fun. Sometimes it can be the difference between something being even possible or not. In this case, the author said they ran this algorithm hundreds of times. So changing it from 30 mins to 0.1 second makes things that were impossible before, possible. I don't find it fun at all to optimise, but I can see where I may need to in order to make things better, or possible at all... what I am suggesting is that anyone writing code, scientist or not, need to be aware of this - and know when they just MUST optimise.
(You don't have to binary search each time, you have a known starting point from the previous point. So you binary search once to find the top-left point and the rest is just checking if you moved into the next global grid cell.)
We frequently have strange issue come in cause fucking vendors can’t stop making changes, then saying “nobody changed anything that would cause that”!
Anyway. I told my boss I’m going to spend a day optimizing this, and got back “we had people try, it’s not really a valuable use of time”.
Long story short, I know that processing 10,000 lines of data should absolutely not take 7 minutes and now it doesn’t. Now we can run our entire day in 30 seconds. No threads. No async. Half a day of optimization.
I think part of the problem with optimization today is that people are completely clueless about how fast computers are, and thus stay happy with being slow just as a matter of ignorance. The FP community pushing that all optimization is premature optimization doesn’t help either.
- Why is it on a timer, instead of being triggered when the first job was done?
- Why was that first job taking over an hour to process XML data?
Turns out the second one was accidentally-quadratic, since it was parsing a whole document to process its first record; then re-parsing the whole document to process its second record; etc. and this made it very slow when the XML was reaching several hundred MB.
Unfortunately, the solution we went with was to set the timer to run 20 minutes later...
I have massive respect for anyone who can pull off steady improvements, no matter how small.
"Use a set instead of a list, it's faster". "that's premature optimization!"
It's fairly easy to remove the set and replace it with something else if this does come up.
Doesn't seem like it'd be the general case though. And it seems like it'd be fairly obvious when something like this applies.
- modularity
- readability
- abstraction
- maintainability
- scalability
- reusability
- testability
- cost
We've all seen codebases that are over-abstracted in the name of code reuse, or to adhere to this or that software design philosophy, and are nightmares as a result. "You Ain't Gonna Need It" is a restatement of the premature-optimization adage, just for a different metric.You can design for modularity, readability etc., but optimising for them doesn't sound right. Something to do with them not having well-defined, agreed-upon definitions of where the optimum is.
For example, modularity could be modelled as the time/edit-distance/etc. it would take to swap-out N out of C components, where C >> N. If we're defining things centrally and passing them around via dependency-injection then it's O(N) (each component is defined once, so change N of them); if we're hard-coding references everywhere then it's O(N*C) since we'll have to check for references in all of the components, and each one needs to be checked for the N possible swaps.
On the other hand, dependency injection is less optimal than hard-coding for a metric like API size: for N components, the number of references grows as O(N^2) (since new components need to reference existing ones, and existing ones will need to reference them); so APIs using DI will have O(N^2) arguments.
But when people warn against "premature optimisation", specifically, they are advising you to give higher priority to things like modularity, readability, abstraction and maintainability, and not sacrifice them all for a singular goal, typically speed.
“The Last Responsible Moment” is subtle. You have to include user benefit (they may not care yet) versus snowballing consequences of delay, versus having more and better information later on.
One of the things that drew me to CI/CD was the notion that you’re not going to get better at handling your problems by avoiding them. You can’t learn to pick your optimization battles if you never fight them. You won’t learn something I’ve been saying for almost 25 years now and I still haven’t heard come out of anyone else’s mouth: there are code changes that improve readability and performance. Making those changes as refactors is hardly ever premature.
Unfortunately not a lot of employers consider this a good use of time.
I am not trying to take anything away from the author but digging into assembly instead of simply looking up the docs seems like a long road to go for calling it an optimization.
Another thing is that why does this even exist ? The tests are showing some sql executions but is that real production executions or simply to test a possible optimization in a different context.
I am not mentioning in as a premature optimization because I am not sure whether I am bright enough to tell what is going on.
Please clarify
Leverage might reside in architectural changes, but one can also imagine other dimensions (not just altitude in application level) that also confer leverage.
Would appreciate it if you could share more.
(I am suffering from back pain from several years)
Wait, what? negative 1.6x?