Live data from Hacker News

“Clean” code, horrible performance

computerenhance.com

871–880 of 932 posts

Re: “Clean” code, horrible performance

#871
post #767

Earlier quoted context omitted.

> The author seems to be neglecting the fact that the whole point of “clean code” is to improve the likelihood of achieving the first goal (code that runs well, i.e. correctly) across months/years of changing requirements and new maintainers. Yes that is the whole point of "clean code". Thing, is, it failed. Simplicity is better achieved with other methods. Forget Uncle Bob and SOLID, read John Ousterhout (A Philosop…

> it failed. That is a large statement you make there. It begs for backing up.

Bob Martin should back his claims up. He asserted many things in his book, without evidence, and with examples so bad most of them actually hurt his case. "Clean Code" is just a bad book, best ignored. Great speaker, though.

---

As for SOLID, there's one good thing: Barbara Liskov. Her principle have mathematical underpinnings in type theory, shapes Haskell's type classes and likely Rust traits too. The rest however ranges from situational to just crap.

Single responsibility is at best a heuristic for the real goal: keeping a nice and small API/implementation ratio. And it fails way too often, causing you to make tiny classes and one liner functions, whose implementations are so tiny they don't even pay for their interface. Pretty bad overall.

The Open/Close principle is just crap. Don't use inheritance if you can help it, and don't bother with keeping your code open for this or closed for that. Just keep it simple, so that when requirements changes you can rewrite the parts you need to rewrite.

Interface Segregation is a situational heuristic. Just keep your interfaces small, and you'll know when it makes sense to split an API in two or not.

Finally Dependency Inversion is cancer. I mean that literally: it causes your code to grow unsightly appendages, makes everything it touch a tad bigger and more complex, and in most cases it doesn't even facilitates testing. Because surprise, the overwhelming majority of the time, code dependencies are fixed. So let them be. Don't complicate your program with interfaces that only have a single implementation. Let your code depend on the implementations directly. It will be simpler, easier to navigate, easier to modify, and just as easy to test.

---

As I said, "Clean Code" failed. Miserably.

Re: “Clean” code, horrible performance

#872
post #848

Earlier quoted context omitted.

> The author seems to be neglecting the fact that the whole point of “clean code” is to improve the likelihood of achieving the first goal (code that runs well, i.e. correctly) across months/years of changing requirements and new maintainers. Yes that is the whole point of "clean code". Thing, is, it failed. Simplicity is better achieved with other methods. Forget Uncle Bob and SOLID, read John Ousterhout (A Philosop…

I'm certainly not going to defend Uncle Bob here, but I don't think you have to be a SOLID cultist to think that preferring smaller functions, factoring out oft-repeated patterns, and relying on a language's abstraction features (e.g. polymorphism) are useful heuristics for managing complexity and maintainability in a codebase. Like, the article's author complains at length about the overhead of a virtual function ca…

> I don't think you have to be a SOLID cultist to think that preferring smaller functions, factoring out oft-repeated patterns, and relying on a language's abstraction features (e.g. polymorphism) are useful heuristics for managing complexity and maintainability in a codebase.

They look like reasonable heuristics indeed, but they're perfectible.

Factoring out repeated patterns, that's good. Keep that one. But I would advise to wait for the pattern to emerge in the first place, so that when you factor it you know exactly what abstraction you need. https://caseymuratori.com/blog_0015 (Semantic Compression)

The size of functions is a fairly poor heuristic. The main size you want to minimise is that of the entire code base. At the function (and class/module) level, it's better to minimise API/implementation ratios. That's how you know the function (or class, or module) is useful: small easy to learn APIs that hide significant implementations give you leverage, and help you minimise what you need to keep in mind whenever you're writing a new piece of code.

Relying on a language's abstraction features… yeah, I guess, though I generally avoid class based polymorphism. It's a bit heavy for my taste, especially when we have the ability to just pass closures around instead. Often that poor man's object is all you need.

> I don't think their critique is really focused on the nuanced differences between the various schools of "how to make your code nicer to read" (at least, I didn't read it that way).

It wasn't indeed, but you'll note his code ended up being quite a bit smaller than the original. All those abstractions are nice when they make your code shorter, or better organised but in this case they just didn't. We could blame the toy nature of the example, but still: all those one liners were terrible for the API/implementation ratio, it's no surprise they could be fused together so concisely.

Re: “Clean” code, horrible performance

#873

Earlier quoted context omitted.

Right? 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.

You are completely changing the problem. Remember, this is an example, the code does the same thing in either implementation.

> 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.

Re: “Clean” code, horrible performance

#874

Earlier quoted context omitted.

It's more than that. The way black box composition is done in modern software, your n=100 code (say, a component) gets reused into a another thing somewhere above, and now you're being iterated through m=100 times. Oops, now n=10k Generally, Casey seems to preach holistic thinking, finding the right mental model and just write the most straightforward code (which is harder than it looks; people get distracted in the…

> The way black box composition is done in modern software, your n=100 code (say, a component) gets reused into a another thing somewhere above, and now you're being iterated through m=100 times. Oops, now n=10k That doesn't seem quite right. as 100 * (100^2) <<<<< 10000^2

Yeah I was only talking about quantities. Equivalently, assume that it's a linear algorithm in the child and a linear one in the parent. Ultimately it ends up as O(nm) being some big number, but when people do runtime analysis in the real world, they don't tend to consider the composition of these blackboxes since there'd be too many combinations. (Composition of two polynomial runtimes would be even worse, yeah.)

Basically, performance doesn't compose well under current paradigms, and you can see Casey's methods as starting from the assumption of wanting to preserve performance (the cycles count is just an example, although it might not appeal to some crowds), and working backward toward a paradigm.

There was a good quote that programming should be more like physics than math.

Re: “Clean” code, horrible performance

#875

Earlier quoted context omitted.

This comment helped make sense of this whole comment section for me. I work in game development, largely with optimisation. I mostly work with GPU optimisation, which is a whole different beast. On the CPU, most of the time issues are either trying to do too much stuff in a hot loop (rendering stuff that could have been culled, putting physics on objects that don't need it,...) or doing something in a slightly ineffi…

Hot loops are where you spend your optimizing efforts. If you're going through that list of shapes again and again it very well might be worthwhile to cache some data and provide the objects with a way to update the cache.

There are a lot of clever techniques already in play to minimise the amount of data you need to consider.

Still, each triangle's position, shape, and other properties can change each frame, as does that of the camera. So you cannot avoid doing some amount of work for each of the visible triangles and their vertices each frame.

Since you need to update the screen at a consistent frequency (typically 30 or 60 times a second) and the list of triangles that actually need to be rendered each frame is in the millions... Well, that's a lot of work which cannot be avoided.

Re: “Clean” code, horrible performance

#876

I don't like most of these "principles", as anyone can verify by looking at my previous comments, but this article is cherry-picking to its utmost level of unfairness. These "clean code" principles should not, and generally are not, ever used at performance critical code, in particular computer graphics. I've never seen anyone seriously try to write computer graphics while "keeping functions small" and "not mixing le…

> These "clean code" principles should not, and generally are not, ever used at performance critical code, in particular computer graphics.

I agree that this is mostly true, but maybe not for beginners in the field

When I was reading "Raytracing in One Weekend" (known as _the_ introductory literature on the topic), I was very surprised to see that the author designed the code so objects extend a `Hittable` class and the critical ray-intersection function `hit` is dynamically dispatched through the `virtual` keyword and thus suffers a huge performance penalty

This is the hottest code path in the program, and a ray-tracer is certainly performance critical, but the author is instructing students/readers to use this "clean code" principle and it drastically slows down the program.

So I agree most computer graphics programmers aren't writing "clean code", but I think a lot of new programmers are being taught them because of introductory literature

Re: “Clean” code, horrible performance

#877

Earlier quoted context omitted.

Profiling. If you're not profiling, you're completely wasting your time. The 1% is almost never where you think it is. And when you do identify the 1%, you need to be testing optimizations with a profiler constantly while optimizing. Profile. Do some optimization. Profile again. Roll back if not successful. Repeat until done. It's impossible to optimize well if you're not doing profiling. The ultimate tools would be…

I'm not familiar with how it compares to ARM/Intel's profiling tools, but I found the Linux perf suite to be very capable (though limited to Linux obviously). And Hotspot [1] allows effortless profile visualization using flame graphs, including some very interesting features such as off-CPU time profiling [2]. "perf record" coupled with Hotspot forms a very smooth edit-compile-profile cycle. [1] https://github.com/KD…

Agreed. Linux perf tools are perfectly acceptable for all but the most ultra-extreme optimization tasks.

VTune allows you to determine where pipeline stalls are occurring at the instruction level (for that last 2 or 3% gain in performance). I haven't worked with ARM profilers (way out of my price range), but I assume, given the exorbitant price, they provide the same sort of in-depth analysis. Probably a handful of people on the planet that need that kind of in-depth analysis.

Re: “Clean” code, horrible performance

#878

Earlier quoted context omitted.

> You'd like to pay money to be assured that you're right? I know I'm right, I'd pay money to see the embarrassment of the presumptuous Clean Code people who think that they can write maintainable code better than those who write software that matters (like Linux or Postgres, as mentioned before). > So, your thesis is that writing a terminal emulator - software which is pretending to be hardware that existed 40+ year…

> I know I'm right, I'd pay money to see the embarrassment of the presumptuous Clean Code people who think that they can write maintainable code better than those who write software that matters (like Linux or Postgres, as mentioned before). You believe you're right, which of course you do, you almost can't help it. Still, you keep mentioning Linux and it's worth a moment to consider that Linux actually does have the…

> Linux actually does have the flavour of problem the Clean Code is modelling here, and it does indeed solve it the way Clean Code recommends

Getting a bit desperate here, eh? On one hand, having tables of function pointers does not introduce any code constraints, you can switch to switches or anything else at a moment's notice; class hierarchies are much more rigid (some random Torvalds quote, "all your code depends on all the nice object models around it, and you cannot fix it without rewriting your app"). On the other hand, Clean Code is fundamentally tied to OOP and classes. Here, straight from the horse's mouth [1]:

"This expectation of polymorphism is the essence of OO programming. It is the reductionist definition; and it is inextricable from OO. OO without polymorphism is not OO. C and Pascal programmers (and to some extend even Fortran, and Cobol programmers) have always created systems of encapsulated functions and data structures. It does not require an OOPL to create and use such encapsulated structures. Encapsulation, and even simple inheritance, is obvious and natural in such languages. (More natural in C and Pascal than the others.) So the thing that truly differentiates OO programs from non-OO programs is polymorphism. You might complain about this by saying that polymorphism can be achieved by using switch statements or long if/else chains within f. This is true, so I must add one more constraint to OO. The mechanism of polymorphism must not create a source code dependency from the caller to the callee."

In short, C is not OO because it doesn't do polymorphism (as understood in the context of Java-like OO languages rather than a mystic "it kinda looks and does the same as OO, thefore C is OO"). Furthermore:

"FP and OO work nicely together. Both attributes are desirable as part of modern systems. A system that is built on both OO and FP principles will maximize flexibility, maintainability, testability, simplicity, and robustness. Excluding one in favor of the other can only weaken the structure of a system".

Which is to say, if you don't do OO then you're not doing Clean Code. On a side note, Robert Martin obviously thinks he could write a Linux that's more flexible, maintainable, testable, simple and robust, but he's leaving it as an exercise to the reader.

https://blog.cleancoder.com/uncle-bob/2018/04/13/FPvsOO.html

> You're spending an unaffordable amount of your finite engineering resource on handling other people's problems in all of your code if you insist on peering inside everything

It's not quite so dramatic, you don't need access to STL's internals, just the structures you're working with anyway. To simplify: if you have an algorithm that deals with shapes then don't abstract away the concrete types, don't try to impose a taxonomy, don't pretend there's a magic shape interface that generalizes everything, don't try to fit the square box in a round hole. Instead, allow the algorithm to deal with concrete types. This is in fact the most flexible approach - you won't find yourself having to rethink your class hierarchy when one of your classes doesn't neatly fit into the general picture. I've been in the situation where at the end of a project it becomes very obvious that the chosen class hierarchy is actually unsuitable for easily adding more features and improving performance, but by that point the effort to restructure the hierarchy is equivalent to a rewrite. But hey, we had Clean Code. The key point is that Casey's approach allows you to easily optimize for performance if needed; Clean Code does not.

> Casey and Jonathan Blow are too slow to deliver products

Fine, take Mike Acton. Same ideas, except he had to ship games on demand. You won't catch him doing Clean Code.

Re: “Clean” code, horrible performance

#879
post #367

Earlier quoted context omitted.

Casey is a bit of a hardcore crusader on the topic, but I'd hardly call dogmatic someone who can provide you evidence and measurements backing their thesis. The tests he put together here are hardly something I'd call a straw-man argument, they seem like reasonable simplification of real-cases.

These examples are absolutely a strawman. He's imagining there's one specific access pattern that's executed thousands of times per second. In a realistic codebase you're accessing the data less often but in multiple different (often subtly so!) ways. Cache efficiency is everything for modern CPUs, so you can't "simplify" the access patterns without making your benchmarks unrepresentative.

What defines a codebase as realistic, exactly? There are many, many programs out there doing various things in various ways.

Re: “Clean” code, horrible performance

#880
post #197

One can be tempted to like any assault on "Uncle Bob"'s insulting videos in the light of working on a codebase where every 2nd line forces you to jump somewhere else to understand what it does. That sort of thing generates a rebellious feeling. OTOH the class design lets someone come and add their new shape without needing to change the original code - so it could be part of a library that can be extended and the ind…

That's one side of the expression problem; the other is adding a new operation. With dynamic dispatch, you can add new shapes without altering the others, but if you want to add a new operation (e.g., perimeter()) then you have to modify the base class and all the children. With discriminated unions, adding a new shape requires modifying all the operations, but adding a new operation only requires the creation of a n…

If adding an operation is the common case then optimise for that. If adding new shapes is the common case then ...

You might feel that there are infinities of potential shapes out there but not really infinities of operations.

Post reply on HN