When working on large applications, by far the single most important factor in performance is having simple and understandable code.
Understandable but slow code can be fixed. Incomprehensible code can't, so it either stays slow or gets worked around with caching/async processing/etc.
If you want fast software, you should write the simplest thing that isn't obviously stupidly slow, then measure and see what parts you need to change. Occasionally you need to make pieces less readable to make them faster, but it's going to be 5% of the application, not the whole thing.
Truly fast software is made by thinking about data flow from the start. If you use the right data structures, the code takes care of itself.
But this is far beyond Clean Code. The examples in that book are neither readable nor performant. He uses bad data structures and hidden mutation everywhere. In the large, that approach leads to a buggy, fragile mess.
I disagree with much of the advice in Clean Code, but it has nothing to do with performance. Clean Code is bad because it produces overly-complex unreadable code. The reason that it produces poor performance isn't because the code is too readable; to the contrary, if the code was more readable it would be more obvious that it's using the wrong data structure.
I'm not saying that you shouldn't think about performance from the beginning. I'm saying that you shouldn't sacrifice simplicity and readability for the sake of performance until you are sure it's necessary because those things are rarely in opposition to each other on the macro scale.
Nothing is worse for performance than doing work you don't need to do and unreadable code tends to do a lot of that if it has been actively maintained for more than a year or two.
I’ve done a lot of work optimising text CRDTs. There, the simple data structure is (essentially) a list which contains metadata for each character. But you’re constantly scanning and inserting into the list. You can improve it in two ways: first, make each list item store the metadata for a connected span of characters. Second, use a b-tree for fast insertion. Make the b-tree store aggregate metadata in internal nodes. That gets you orders of magnitude better performance - O(n) per keystroke to O(log n). RLE gets ~10x lower ram utilisation. It’s only the obvious data structure when you’ve thought about the problem a lot. And you have to write your own btree - there are no libraries for this. At least, none I have found.
> I'm saying that you shouldn't sacrifice simplicity and readability for the sake of performance until you are sure it's necessary
It really depends on the domain. If you’re making a note taking app, you probably get good enough performance by doing the obvious thing. If you’re making a browser, database, llm inference engine or 3d game engine, it pays to think about perf from the start. But my impression is that most people on this site aren’t doing that sort of thing.
The vast majority of applications are better off using one of the many high-performance, battle-tested implementations of b-trees that already exist, which, for users of those implementations, is one of the simplest and most commonly used data structures; we just call them databases and filesystems instead of b-trees.
Every rule has exceptions but you should know the rules before you decide to break them. For anyone other than an experienced expert, writing your own b-tree implementation in a production system is an extremely foolish decision (if it's for fun or learning, do whatever you want).
I think I broadly agree with your overall point. I’ve just spent a lot of my career working on niche problems like this. And there are a lot of people working on systems software. Windows, Linux, macOS, chrome, postgres, etc don’t write themselves. But unless you move in those circles, you can spend your whole life never interacting with any of those engineers.
> we just call them databases and filesystems instead of b-trees.
The b-trees I’m talking about are in memory. Btrees often outperform other kinds of in memory tree structures (avl, rb, binary, etc) because you get fewer dram memory stalls.
Clean code like approaches have a very real cost for users (even in languages with good optimizers) and is usually unfixable after the fact.
No. You may be sacrificing end user experience, but it's not guaranteed. You have to examine the system under development to determine which style is appropriate.
If you actually have to process huge numbers of these objects, then yes. But if you don't, if whatever the actual real-world object is trickles in at 10 per second, do you need to worry about performance and cache misses here? You're already going to suffer from cache misses because the processing rate is so low.
So you get to make an engineering choice based on circumstances. If you need high-throughput, use a design that satisfies that requirement but maybe forfeits flexibility and maintainability. If you don't, then you can lean towards a design that forgoes a bit of performance in favor of flexibility and maintainability.
Use your judgement, don't follow any rule blindly whether it comes from Muratori or Martin.