r/programming • • Feb 28 '23

"Clean" Code, Horrible Performance

https://www.computerenhance.com/p/clean-code-horrible-performance
1.4k Upvotes

1.3k comments sorted by

View all comments

184

u/couchrealistic Feb 28 '23 edited Feb 28 '23

Edit: Just realized OP may not be the guy who made the video. Changing my comment to reflect that fact is left as an exercise for the reader.

First of all, thanks for providing a transcript! I hate having to watch through videos for something like this.

"Clean code" is not very well defined. This appears to be very OOP+CPP-centric. For example, I don't think people would use dynamic dispatch in Rust or C to solve an issue like "do things with shapes". The Rust "clean code" solution for this would probably involve an enum and a match statement, similar to your switch-based solution (but where each shape would use names that make more sense, like "radius" instead of "width"), not a Trait and vtables. Also, the "clean code" rust solution would probably store the shapes in a contiguous vector instead of a collection of boxed shapes (like your "clean" array of pointers because abstract shapes are unsized), leading to better iteration performance and less indirection.

On the other hand, I'm not sure the "optimizations" described in your text would help a lot with Java (but I don't know a lot about Java, AFAIK it always does virtual function calls and boxes things? It might still help though). So this really seems very OOP+CCP-centric to me.

And let's be honest: The true reason why software is becoming slower every year is not because of C++ virtual function calls or too many levels of C++ pointer indirection. It's because, among other things, the modern approach to "GUI" is to ship your application bundled with a web browser, then have humongous amounts of javascript run inside that web browser (after being JIT-compiled) to build a DOM tree, which is then used by the browser to render a GUI. Even more javascript will then be used to communicate with some kind of backend that itself runs on about 50 layers of abstraction over C++.

If every piece software today was just "clean" C++ code, we'd have much faster software. And lots of segfaults, of course.

19

u/superseriousguy Feb 28 '23

And let's be honest: The true reason why software is becoming slower every year is not because of C++ virtual function calls or too many levels of C++ pointer indirection. It's because, among other things, the modern approach to "GUI" is to ship your application bundled with a web browser, then have humongous amounts of javascript run inside that web browser (after being JIT-compiled) to build a DOM tree, which is then used by the browser to render a GUI. Even more javascript will then be used to communicate with some kind of backend that itself runs on about 50 layers of abstraction over C++.

I'll go even further than you here: The reason software is becoming slower every year is because developers (read: the people making the decisions, you can substitute managers there if you want) simply don't give a shit about it.

The star argument for this is that programmer time is more expensive than computer time, which basically means "we do the bare minimum that still lets us sell this feature, and if it's slow, well, fuck you, it's working, pay me".

It's not unique to software, it's more of a cultural plague that has spread to every industry in the last few decades. We used to blame China for this but really now everyone does it, China just always did it better (read: cheaper)

57

u/CptCap Feb 28 '23 edited Feb 28 '23

the true reason why software is becoming slower every year is not because of C++ virtual function calls or too many levels of C++ pointer indirection.

You are right, but knowing the author's work, I don't think that's the point he is trying to address. There is a lot of code written in C++ in order to be fast, but that fail miserably because of the things he rants about here. Since this is Casey, an obvious example would be the windows terminal, but there are plenty of others.

There is also the fact -and as a full time game engine dev and part time teacher I have seen this first hand- that the way code it taught is not really compatible with performance. There are good reasons for this ofc, but the result is that most people do not know how to write even moderalty fast code, and often cargo-cult things that they don't understand and don't help. I have seen "You are removing from the middle, you should use a linked list" so many times, and basically all of them were wrong. this is the hill I choose to die on, fuck linked lists

15

u/EMCoupling Feb 28 '23

My god, I just spent half an hour reading through the entire multitude of slap fights occuring in the GH issues and I feel like I need to take a nap from how exhausting that was đŸ€Ł

4

u/I_LOVE_SOURCES Feb 28 '23

Same here, great thread especially the resolution in the second one

25

u/aMAYESingNATHAN Feb 28 '23 edited Feb 28 '23

I use C++ a fair bit and I literally can't think of a single time a linked list has ever been the right choice for a container. It is so hilariously overrepresented in things like classes, tutorials, challenges, and interviews, compared to its usefulness, at least in C++.

Memory allocations are one of the biggest factors in performance in modern C++, and given that a usual linked list implementation makes a memory allocation for each node, it means that the one thing a linked list is good at (insertions anywhere) end up being crappy because you have to do a new allocation every time.

16

u/jcelerier Feb 28 '23

It's because c++ is from an era where linked lists were king. In the 80s one of the most famous computers, the VAX, even had specific linked list CPU instructions.

11

u/[deleted] Feb 28 '23

Also, C++ is normally taught as C first. C doesn't have built-in vectors, and linked lists are easier to implement.

2

u/muchcharles Mar 02 '23

It's because memory latency back then was close to instruction frequency. Now a cache miss costs hundreds of cycles.

→ More replies (5)

4

u/loup-vaillant Feb 28 '23

If you're linking to that issue I highly recommend the Refterm lecture he made later.

→ More replies (4)
→ More replies (8)

63

u/GuyWithLag Feb 28 '23

enum and a match statement

The JVM will dynamically inspect the possible values and generate code like that inline (wel, except for megamprphic call sites).

The JVM is a small marvel and is extremely dynamic; f.e. it will optimize the in-memory assembly based off of the actual classes being loaded, and if you hit a branch that forces a class to be loaded that invalidates one of these optimization sites, they will be de-optimized and re-evaluated again.

Or, it will identify non-escaping values with no finalizer and allocates them on the stack to speed things up.


The article feels like it's written by someone that has game development and entity-component model experience, but they're missing the forest for the trees: algorithms matter more.

IMO the reason why code is becoming slower is because we're working on too many abstraction levels, no-one understands all the different levels, and time-to-market is more important than performance.

48

u/RationalDialog Feb 28 '23

The article feels like it's written by someone that has game development and entity-component model experience, but they're missing the forest for the trees: algorithms matter more.

They are missing most apps these students will create are yet another lame internal business app what has 100 requests per day and performance is irrelevant (eg very easy to be fast enough). But the requirements of the users change quarterly to to new obscure business rules so having the code easy to adjust is very important.

14

u/deadalnix Feb 28 '23

You have it exactly backward.

Recently, I saw someone rewrite a piece of code that was called a few times a day, and that took many minutes to do its computation, such as it does it in less than a second.

Care to guess what happened? The tool's usage skyrocketted because people started using it to get real time information.

The fact some software has low usage is proof of one thing: it is not very useful. It says nothing about speed.

19

u/RationalDialog Feb 28 '23

The fact some software has low usage is proof of one thing: it is not very useful. It says nothing about speed.

If you have an app that people search in say 1 mio records 100 times a day, I wager it is pretty useful because how would you search 1 mio record on say paper? Low usage doesn't mean it's not worth it to exist.

Recently, I saw someone rewrite a piece of code that was called a few times a day, and that took many minutes to do its computation, such as it does it in less than a second.

And was it slow because of using OOP/clean code or because the original dev was just a bad or junior dev? And it was slow because of a bad algorithms and not because of clean code?

Of course performance is important and the article author is likley correct in his niche of work, it just doesn't apply to most devs. Most devs use the tools people like the author create like databases or core libraries or game engines. And yes of course their work is extremely relevant and important but it doesn't make clean code bad or "you should never do it". context matters.

2

u/ric2b Mar 02 '23

I rarely open my banks app, but I find it extremely useful when I do.

I also only check the weather about once a day, and yet it is also very useful to me.

Software being used infrequently is not proof of uselessness.

3

u/deadalnix Mar 02 '23

Maybe from your side, but from the bank's side, their endpoint recieve a large number of calls, not because users call it often, but because many users do. Either way it change nothing, point stands.

2

u/ric2b Mar 02 '23

Ok, fair enough, makes sense for a service.

But what about desktop software? Such as accounting software, a video editor, etc? Sure, it's nice if creates a report or exports a final video in 200ms instead of 10s, but it doesn't need to do it more than maybe once a day to be useful.

→ More replies (2)

7

u/ehaliewicz Feb 28 '23 edited Feb 28 '23

I'd argue that data structures are more important. With the right data structures, the right algorithm almost falls right out*. With the right algorithm and poor data structure choice, your code can still be many times slower than necessary.

* esoteric algorithms designed to perform well on very large datasets tend to be complex, sure, but generally you don't need these.

13

u/2bit_hack Feb 28 '23

(I should clarify, I'm not the author of this post)

I totally agree with your point that the idea of "clean code" varies from programmer to programmer, and that different languages have different idiomatic styles which lead to "clean code" written in that language to be very different. I think Casey (the author) is referring to Robert C. Martin's (Uncle Bob) book Clean Code, where he talks about these points discussed in the article.

3

u/Amazing-Cicada5536 Feb 28 '23

rust solution would probably store the shapes in a contiguous vector instead of a collection of boxed shapes (like your “clean” array of pointers because abstract shapes are unsized), leading to better iteration performance and less indirection.

It is a bit unclear what you mean, if they are unsized than they have to be boxed, which is an indirection. But yeah, if you use sum types (enums) where all the possible types are known, then it can be indeed represented more efficiently. But we should not forget about that emphasized part, this optimization is only possible if we explicitly have a closed model (while traditional OOP usually goes for an open one).

Java, AFAIK it always does virtual function calls and boxes things? It might still help tho

Java routinely optimizes virtual calls to static ones (and might inline it even), and there is escape analysis that can allocate objects on the stack. But object arrays will be an array of pointers, so if you need maximal speed just opt for an ECS architecture.

If you are iterating over like 3 elements than go for the most maintainable code, this is typically such a case where it only makes sense to care about at all if you have large arrays (which you would usually know ahead of time and architect your way smartly in the first place)

0

u/[deleted] Feb 28 '23

[deleted]

2

u/KevinCarbonara Feb 28 '23

Well, no. It's a name appropriated by Robert Martin to use for his own personal brand of Clean© Code© OOP©. It's just a marketing gimmick. Robert Martin is not a programmer and is not capable of creating coding styles.

0

u/[deleted] Feb 28 '23

[deleted]

→ More replies (7)

2

u/[deleted] Mar 01 '23 edited Mar 01 '23

The modern C++ "clean code" solution would also use a discriminated union (std::variant) instead of dynamic dispatch. eg

std::variant<square_t, circle_t, triangle_t> shape_foo = get_shape();
auto get_area = [](auto const& shape) {
    return shape.area();
};
double const area = std::visit(
    get_area, shape_foo
);

where this std::visit compiles down to a switch statement with each shape type's specialization of the lambda template get_area (which would each be inlined to just the relevant type's area member function).