So DRY isn’t an obvious default for me.
So DRY isn’t an obvious default for me.
There is no question in my mind that this should always have been built as either a single pipeline with some parameters to cover the two use-cases, or perhaps as a toolbox of reusable components that are then used for the overlapping area. But I expect the mentality at the time the second one was being stood up was that it would be less disruptive to just build the new stuff as a parallel implementation and figure out later how to avoid the duplication.
More importantly, cleverer people than me said "premature optimization is the root of all evil"
In raw performance this could manifest as issues with data duplication bloating structures and resulting in cache misses, generic structures expressed in JSON being slower then a purpose-built struct, chasing pointers because of functions buried in polymorphic hierarchies. But I doubt that any of this would really matter in 99% of applications.
In some cases it makes sense, like writing SIMD/etc. specific assembly for compression/decompression or video/audio codecs, but more often than not the readable version is just as good -- especially when compilers can do the optimizations for you.
A lot of times I've found performance increases have come from not duplicating work -- e.g. not fetching the same data each time within a loop if it is fixed.
Also, your digression about modern compilers is irrelevant to the context of the quote, since Knuth talked about premature optimization at a time when compilers were much simpler than today.
If you measure the code's performance and see the ++i/i++ is consuming a lot of the CPU time then by all means change it, but 99% of the time don't worry about it. Even better, create a benchmark to test the code performance and choose the best variant.
But it's your life to live. Who cares what someone else thinks?
If you're talking about consolidating some code from one in-process place to another in the same language, you're mostly right: there's only going to be an optimization/performance concern when you have a very specific hotspot -- at which point you can selectively break the rule, following the guidance you quoted. This need for rule-breaking can turn out to be common in high-performance projects like audio, graphics, etc but is probably not what the GP had in mind.
In many environments, though, DRY'ing can mean moving some implementation to some out-of-language/runtime, out-of-process. or even out-of-instance service.
For many workloads, the overhead of making a bridged, IPC, or network call swamps your algorithm choice and this is often apparent immediately during design/development time. It's not premature optimization to say "we'll do a lot better to process these records locally using this contextually tuned approach than we will calling that service way out over there, even if the service can handle large/different loads more efficiently". It's just common sense. This happens a lot in some teams/organizations/projects.
A really common example is overhead of polymorphism, although that overhead can vary a lot between stacks. Another is just the effect caused by the common complaint about premature abstraction: proliferation of options/special cases, which add overhead to every case even when they don’t apply.
premature abstraction -> not understood dry (https://news.ycombinator.com/item?id=40525064#40525690)
If you want a specific example look at something that needs to be performant, i.e. in those 3%, let's say OpenSSL's AES implementation for x86, or some optimized LLM code, you'll see the critical performance sections include things that could be reused, but they're not.
Also the point Knuth is making is don't waste time on things that don't matter. Overuse of DRY falls squarely into that camp as well. It takes more work and doesn't really help. I like Go's proverb there of "A little copying is better than a little dependency."
Source: Donald Knuth on the Lex Fridman podcast, when Lex asks him about that phrase
It might not specifically be DRY, but still related generic vs specialized code/systems.
Only in critical regions of code though.