56 ms·
“Clean” code, horrible performance
- evalda 4y agoApart from performance, I find that "non-clean" code (in terms of the article) is sometimes easier to understand, reason about and maintain. Context is important of cause...
- hvasilev 4y agoYea, me too. I don't really understand people that say that clean code is easier to understand.
- kybernetyk 4y agoI love by a simple rule: is code being called repeatedly many times a second? Make it performant. Is code called rarely? Make it clean
- DeathArrow 4y agoIf null was one billion dollar mistake, clean code, SOLID and design patterns are 10 billion dollar mistakes. Think of all CPU cycles wasted across all the data centers and user's devices.
- gregjor 4y agoBrilliant. Thanks. tl;dr polymorphism, indirection, excessive function calls and branching create a worst-case for modern hardware.
- devdude1337 4y agoMost C++-projects I dealt with are neither clean nor performant. I rather follow clean code to improve maintainability and get things done than to optimize for performance. It’s also easier to find bottlenecks in a well readable and testable code base than in a premature-optimized one. However it is true that the more abstractions and indirections used software gets slower. Also these examples are too basic to make a real-world suggestion: never assume sonething is slow in a large project because of indirection or something…always get a profiler involved and run tests with time requirements to identify and fix slow running parts of a program.
- fredrikholm 4y agoI've worked in projects where no one seemed to know SQL, where massive speed improvements were made by fixing very low hanging fruits like removing select * queries, adding naive indexes, removing N+1 queries etc. Likewise, I've worked in code bases where performance had been dreadful, yet there were no obvious bottlenecks. Little by little, replacing iterators with loops, objects/closures with enum-backed structs/tables, early exits and so on accumulating to the point where speed ups ranged from 2X to 50X without changing algorithms (outside of fixing basic mistakes like not pre allocating vectors). Always fun to see these videos. I highly recommend his `Performance Aware Programming` course linked in the description. It's concise and to the point, which is a nice break from his more casual videos/streams which tend to be long-winded/ranty.
- pocket_cheese 4y agoCould you elaborate what you mean by "naive indexes"?
- fabrice_d 4y agoIn general that means looking at the sql query plan for a slow query, and adding appropriate indexes when there are full table scans.
- ocimbote 4y agoI assume "naive" means "simple, basic" here. As in "index this column. Period".
- dgb23 4y agoNot GP. But you can often anticipate where indexes have to go for common queries. There might be some important colums like say a “status” or a “date”, which are fundamental to a lot of queries. Or you have colums X and Y being used frequently or importantly in where clauses together, then that’s a candidate for composite indexes. Stuff like that.
- hinkley 4y ago
- athanagor2 4y agoThe performance difference is truly savage. But I think there is a reason for the existence of "clean code" practices: it makes devs easier to replace. Plus it may create a market to try to optimize intrinsically slow programs!
- spoiler 4y agoIt doesn't just make devs easier to replace. It makes it my job more pleasant (and that of my colleagues). But yes, you're right. It does also help onboard people. Imagine working as a barista with a disorganised bar, a mat on the floor that keeps sliding and a corner is sticking up, and one bag of beans where half the side is decaf and the other is normal. Now compare that to working in a more common sense coffee shop: everything is in its place, the mat isn't decrepit, and you have multiple bean bags. In which one do you think it's easier to make coffee?
- js6i 4y agoHuh? Coffee shops optimize for people not bumping into each other and having related items close together, and don't pretend to not know what kind of gear they have.. that's not a terrible analogy to the exact opposite argument.
- tizzy 4y agoI think the sentiment is that order and organisation is helpful in achieving goals and cultivating a good working environment as opposed to a big mess. Analogies, just like abstractions, are leaky.
- TeMPOraL 4y agoYeah, but this one leaks a smart-matter paint that self-assembles into a shape of text saying "the order and organization is not the goal, but a consequence of ruthlessly optimizing for performance above all".
- 4y ago
- fer 4y agoUnrelated to the content itself, am I the only one wondering if he has his t-shirt mirrored or if he's really skilled at writing right-to-left? Content wise: his examples show such increases because they're extremely tight and CPU-bound loops. Not exactly surprising. While there will be gains in in larger/more complex software by throwing away some maintainability practices (I don't like the term "clean code"), they will be dwarfed by the time actually spent on the operations themselves. Just toss a 0.01ms I/O operation in those loops; it will throw the numbers off by a large margin, then one would just rather pick sanity over the speed gains without blinking. That said, if a code path is hot and won't change anytime soon, by all means optimize away. Edit: the upload seems to have been deleted.
- vblanco 4y agoits custom made mirrored tshirt, he is writing on one of those lightboards and then mirroring.
- mariusor 4y agoHe created the t-shirts specially for StarCode Galaxy[1] which is a longer form class for C++ programming in the same veign as the current video, but with a much wider scope. (As far as I know SCG is not released yet). As a small, amusing, s(n)ide note, Casey's rants about the slowness of the Windows terminal[2] that ended up in Microsoft releasing an improved version[3], were based him wanting to implement a TUI game as an exercise in SCG and the terminal being too slow. [1] https://starcodegalaxy.com/ https://starcodegalaxy.com/ [2] https://news.ycombinator.com/item?id=31284419 https://news.ycombinator.com/item?id=31284419 [3] https://news.ycombinator.com/item?id=31372606 https://news.ycombinator.com/item?id=31372606
- _dain_ 4y ago>Just toss a 0.01ms I/O operation in those loops; it will throw the numbers off by a large margin, then one would just rather pick sanity over the speed gains without blinking. I mean, yes, if you do something completely fucking idiotic like put an IO operation inside a tight calculation loop, then all your speed gains will vanish. But I don't see how that refutes anything.
- Arnavion 4y ago"Use subclasses over enums" must be some niche advice. I've never heard it. The youtuber seems to be referring to some specific example (he refers to specific advice from "them") so I guess there's some context in the other videos of the series. re: the speedup from moving from subclassing to enums - Compiler isn't pulling its weight if it can't devirtualize in such a simple program. re: the speedup from replacing the enum switch with a lookup table and common subexpression - Compiler isn't pulling its weight if it can't notice common subexpressions. So both the premise and the results seem unconvincing to me. Of course, he is the one with numbers and I just have an untested hypothesis, so don't believe me.
- anonymoushn 4y agoI think "them" is someone named Robert Cecil Martin, but I'm not sure if this example from the video appears in his book. What compiler are you using that devirtualizes every class hierarchy? I suspect that Casey is using C++ so he may (unfortunately) have multiple translation units in his program.
- Arnavion 4y ago>I suspect that Casey is using C++ so he may (unfortunately) have multiple translation units in his program. Yes, the video uses C++. Obviously if one compiles a library then the compiler has no way of knowing that other subclasses of `shape_base` do not exist. My point is that when compiling a binary as they are doing for their video, the compiler knows that there are no other subclasses that it needs to cater to. It might require LTO explicitly, of course. At the very least godbolt doesn't devirtualize without LTO [1], but godbolt itself breaks if I enable LTO [2] and I CBA to test locally right now. [1]: https://gcc.godbolt.org/z/498nKEzhK https://gcc.godbolt.org/z/498nKEzhK [2]: https://gcc.godbolt.org/z/WqhP3fzxK https://gcc.godbolt.org/z/WqhP3fzxK
- Tomis02 4y agoCorrect me if I'm wrong. Even if the compiler devirtualizes the classes, you still have the memory cost of storing the vtable pointer in each of the object instances (8 bytes for each instance), which means you need to do more fetches from memory. Does CPU prefetching negate the cost of these additional memory lookups?
- vrnvu 4y agoThe problem with the contemporary "clean code" concept is that the narrative that performance and efficiency don't matter has been pushed down the throat of all programmers. Re-usability, OOP concepts or pure functional style, design patterns, TDD or XP methodologies are the only things that matter... And if you use them you will write "clean code". Even worse, the more concepts and abstractions you apply to your code the better programmer you are! If you look at the history of programming and classic texts like "the art of programming", "sicp", "the elements of programming"... The concept of "beautiful code" appears a lot. This is an idea that has always existed in our culture. The main difference with the "clean code" cult is that "beautiful code" also used to mean fast and efficient code, efficient algorithms, low memory footprint... On top of the "clean code" concepts of easy to test and re-usable code, modularity... etc
- hennell 4y agoI think he's really underplaying the main selling point of clean code - the objective of writing clear maintainable, extendable code. His code was faster, but sometimes how it compares for adding new features or fixing bugs by people new to a code base is where you want to optimize. Should performance be talked about more? Yes. Does this show valuable performance benifits? Also yes. Is performance where you want to start your focus? In my experience, often no. I've made things faster by simplifying them down once I've found a solution. I've also made things slower in order to make them more extendable. If you treat clean code like a bible of unbreakable laws you're causing problems, if you treat performance as the be-all-end-all you're also causing problems, just in a different way. It's given me something to think about, but I wish it was a more fair handed comparison showing the trade offs of each approach.
- anonymoushn 4y agoIf someone is new to the codebase, would you rather they need to open a dozen files to see all of the different virtual functions that could occur at one call site, or open one file?
- xnickb 4y agoI see your point, but no one uses Windows notepad for coding anymore. There are far worse crimes than having code structure that spans several files.
- Tomis02 4y ago> There are far worse crimes than having code structure that spans several files. Sure, but what's the advantage of having your code split over several files? Yes, you can jump between them with an IDE, but that's still a disruption, and it makes it harder to see common patterns that can help you simplify the code. As demonstrated in the video, splitting up the switch into multiple classes only hurts readability.
- kaba0 4y ago
- mulmboy 4y agoReally enjoyed watching some guy Breathlessly discover data oriented design https://en.wikipedia.org/wiki/Data-oriented_design https://en.wikipedia.org/wiki/Data-oriented_design there's nothing novel in this video, really nothing to do with clean code. This is same sort of thing you see with pure python versus numpy
- drainyard 4y agoYou could spend 2 minutes looking up who you are talking about. This video from 2015 where Casey is interviewing Mike Acton: https://www.youtube.com/watch?v=qWJpI2adCcs https://www.youtube.com/watch?v=qWJpI2adCcs Also this course where he specifically talks about Numpy and pure Python: https://www.computerenhance.com/p/python-revisited https://www.computerenhance.com/p/python-revisited
- vore 4y agoThis guy is so dogmatic about it it hurts. I would argue that clean code is a spectrum from how flexible vs how rigid you want your abstractions to be. If your abstractions are too flexible for good performance, dial them back when you see the issue. If your abstractions are too rigid for your software to be extendable, then introduce indirection. We can all write code that glues a very fixed set of things end to end and squeeze every last CPU cycle of performance out of it, but as we all know, software requirements change, and things like polymorphism allow for much better composition of functionality.
- anonymoushn 4y agoIs there any flexbility tradeoff at all here?
- vore 4y agoThe shapes example is pretty contrived so I don't really have an opinion on it either way. But imagine you have something like a File interface and you have implementations of it e.g. DiskFile, NetworkFile, etc., and you anticipate other implementors. Why would you do anything other than have a polymorphic interface?
- anonymoushn 4y agoI don't know, I've never done anything where code needed to have runtime dispatch on the kind of file it has without also knowing anything about what kind of file it has.
- vore 4y agoHave you never used C++ iostreams? Or the Python file abstraction? Or Rust std::io::Read/std::io::Write? Or Node.js streams? Or DOM Web Streams? Or Ruby files? Or Haskell conduits? Or hell, fopencookie/funopen in C? The abstraction is super common and allows you to connect streams to each other without worrying about the underlying mechanism, which 99% of the time I don't really think you want to worry about unless you're sure it's a performance overhead. And that's great, because I surely don't want to write specializations by hand for all the different combinations of streams I need to use if I don't have to.
- gammalost 4y agoThe video was removed and reuploaded. Here is the new link https://www.youtube.com/watch?v=tD5NrevFtbU https://www.youtube.com/watch?v=tD5NrevFtbU
- jasode 4y agoThe original submitted link was a youtube video that's been deleted for some reason. Probably a better link is the blog post because the author updated it with the new replacement video a few minutes ago as of this comment (around 09:12 UTC): https://www.computerenhance.com/p/clean-code-horrible-performance https://www.computerenhance.com/p/clean-code-horrible-perfor...
- tippytippytango 4y agoI wish software engineering cared a lot more that we have no way of measuring how clean code is. Much less any study that measures the tradeoffs of clean code and other concerns, like a real engineering discipline.
- robalni 4y agoThe funny thing is that the things that are not possible to measure will be undone all the time because people can't agree on how it should be. This means that there will be wasted time. First we have to write the code this way. Next year we have to write it in the other way. Then it has to be done in the first way again. It's so hard to prioritize things when you ask someone why something has to be done the way they say and they are not able to give a real answer. I can do my job when option A means faster program and option B means more memory usage but I can't do my job when option A means faster program and option B is just the way it "should" be done.
- tippytippytango 4y agoThis happens because software engineering doesn’t yet know the difference between principles and morals.
- formvoltron 4y agohttps://www.youtube.com/watch?v=tD5NrevFtbU https://www.youtube.com/watch?v=tD5NrevFtbU
- suyjuris 4y agoIt is also important to consider that better performance also increases your productivity as a developer. For example, you can use simpler algorithms, skip caching, and have faster iteration times. (If your code takes 1min to hit a bug, there are many debugging strategies you cannot use, compared to when it takes 1s. The same is true when you compare 1s and 10ms.) In the end, it is all tradeoffs. If you have a rough mental model of how code is going to perform, you can make better decisions. Of course, part of this is determining whether it matters for the specific piece of code under consideration. Often it does not.
- robalni 4y agoYes. When I worked as a game engine programmer, two of the first things I did were to improve the speed of compiling and starting the games. When those two things are faster, all development will be faster and more fun.
- Quarr 4y agoThis is definitely not something that should be overlooked. Choosing a more mathematically optimal algorithm might be 2-3x faster in theory (at the cost of more complexity). If you're executing that algorithm a lot, to the point where a 3x speedup is significant, well -- if you can restructure the code in a manner similar to that demonstrated in the article (avoiding costly vtable dispatches, indirection, etc) and achieve a 25x with the original simpler algorithm, then that's something worth taking into consideration. A 3x algorithmic improvement is only impressive if there isn't 25x potential speedup low-hanging fruit (from simply not writing your code in a moronic way in the first place).
- hotBacteria 4y agoWhat is demonstrated here is that if you understand well the different parts of some code, you can recombine them in more efficient ways. This is something very good to have in mind, but it must be applied strategically. Avoiding "clean code" everywhere won't always provide huge performances win and will surely hurt maintainability.
- theknarf 4y agoThis seems more like an argument against the object oriented model of C++ than anything else. Would have been more interesting if the performance was compared to languages like Rust.
- bombolo 4y agoI think it could be ok to have a link every once in a while that doesn't talk about rust.
- zaphodias 4y agoWe're talking about C++ and performance, no way nobody would mention rust :P
- abyesilyurt 4y agoMy key learning is the importance of balancing performance and code cleanliness instead of blindly adhering to clean code principles.
- Ygg2 4y agoIf you optimize for readability performance would suffer. If you optimize for performance readability will suffer. Casey prizes performance over everything else.
- pkolaczk 4y agoIn this particular case I find the code optimized for speed (the one using switch) to be also more readable and simpler than the code using virtual dispatch. The problem with virtual calls in a big project is that there is no good way of knowing what is the target of the call, without some additional tooling like IDE. But in case of a switch/if, it is pretty obvious what the cases are.
- karma_fountain 4y ago
- juliangmp 4y agoThis example exists in such a vacuum and is so distant from real software tasks that I just have to shake my head at the "clean code is undoing 12 years of hardware evolution"
- rafabulsing 4y agoIt's an example from the Clean Code book itself, though?
- rileymat2 4y agoIt is, but that's the catch. It would be exceedingly hard to provide understandable examples of these things in code that is complex enough to need the patterns.
- Semaphor 4y agoHere is the link as an article for people like me who don’t like watching videos: https://www.computerenhance.com/p/clean-code-horrible-performance https://www.computerenhance.com/p/clean-code-horrible-perfor...
- davnicwil 4y agoI don't think there is a contradiction or surprising point here. At least my understanding of the case for clean code is that developer time is a significantly more expensive resource than compute, therefore write code in a way which optimises for developers understanding and changing it, even at the expense of making it slower to run (within sensible limits etc etc).
- goodlinks 4y agoExternalised cost on the environment will hopefully one day be addressed.
- TeMPOraL 4y agoNot to mention the users. For all the bullshit the marketing departments of every company spew about valuing their customers, software companies don't really give a damn about the many person-hours of users' lives they waste to save a person-minute of dev time.
- davnicwil 4y agoEverything is a balance though, and the tradeoff may not be linear. Where they've landed on it may be the only way to get the software to customers at all in a cost effective manner.
- pkolaczk 4y ago> developer time is a significantly more expensive resource than compute Depends on the number of invocations of the program.
- MonkeyClub 4y agoNot just that. The “developer time is valuable” mantra is thrown left and right, disregarding how much of that valuable resource will be wasted down the line due to bad implementations. If we optimize for developer time, let’s optimize across the software’s entire lifecycle, not just that first push of a MVP to production.
- datadeft 4y agoTL;DR: "Game developer optimizes code for execution as opposed to readability that 'clean-code' people suggest". There are few considerations: - most code is not CPU bound so his claims that you are eroding progress because you are not optimizing for CPU efficiency is baseless - writing readable code is more important than writing super optimal code (few exceptions: gaming is one) - using enums vs OOP is not changing the readability at least to me I think we can have fast and readable code without following the 'clean-code' principles and at the end it does not matter how much gain we have CPU cycle-wise.
- ruicaridade 4y agoI'm tired of every tool I install on my latest gen Intel CPU + 32GB RAM + NVMe drive machine being a complete slog. To each their own, but I don't find Casey's performant version less readable, I don't see the need for so many abstractions.
- randomdata 4y ago“CLEAN” doesn’t care so much about readability, but rather testability. The video would have been far more compelling if Casey had spent more time showing how he would test his application without the test surface area blowing up exponentially as the feature space expands.
- sebstefan 4y ago>I don't find Casey's performant version less readable It does create implicit coupling. If you try to add a new shape you will run into the problem. In the clean code version, your compiler will remind you to implement calculateArea With his version you have to add a new `case` to every switch statement and hope you didn't miss one with a default case, because the compiler won't catch this one. It's a crap way to code
- Tenticles 4y agopeople using clean code ideologies are being prematurely pessimistic and assuming they know much more about a problem than they actually do when they use these clean code techniques. "I don't know how many shapes I've been asked to do, so I'll assume the worst case scenario and make the code slower and harder than the simple naïve solution that would be hard to read(debatable) if we had one million shapes" is a terrible argument and it is why everything goes slow. The correct way to deal with this, is refactoring to a more maintainable code once you know the amount of shapes will wildly change, As soon as we get too many shapes as the problem has changed. You can only pretend to know what is the best architecture for a problem when you have dealt with it several times. Clean code apologists pretend their single time dealing with website backend is proof enough that clean code works and that it works for every problem and that it has to be the default approach and is the most readable for most problems. It is a total insanity for something that can't be measured with any tool. Edit: I fully understand that "premature optimization is wrong" but using these "guidelines" is premature optimization of scalability and maintainability. Somehow when the "premature optimization" is about things you people want that's somehow okay? pff Also, I don't find clean code readable, it looks like complex, un-refactorable garbage to me 9 out of 10 times. No wonder why people are so fucking scared of rewriting a class and act is if it will take months to do so, this ideology makes impossible to actually play around with your code, you can't neither make it more readable or more performant, you are locked in with a sluggish collection of dozens of files even for the simplest of problems.
- flippinburgers 4y ago"Clean code", "Clean architecture", "Clean etc", they are all totally grotesque and the sign of incompetence.
- thom 4y agoThere is no doubting Casey's chops when he talks about performance, but as someone who has spent many hours watching (and enjoying!) his videos, as he stares puzzled at compiler errors, scrolls up and down endlessly at code he no longer remembers writing, and then - when it finally does compile - immediately has to dig into the debugger to work out something else that's gone wrong, I suspect the real answer to programmer happiness is somewhere in the middle.
- krapp 4y agoIf you're talking about Handmade Hero, the real answer to programmer happiness is not using a language you despise and refusing to leverage the features of, not refusing to use libraries in that language or frameworks, not re-implementing everything from first principles, and to actually have your game designed first (not designing while you code.)
- AnIdiotOnTheNet 4y agoCasey is a bad example of a game designer and he'll be the first to admit it. However, it is worth noting that Jonathan Blow very much does design while he codes and recommends the practice. He also generally abstains from library dependencies and implements a lot of thing himself. Of course, part of the point of Handmade Hero is to show that you can totally reimplement everything from first principles. Libraries are not magical black boxes, they're code written by human beings like you or me, and you can understand what they're doing. For instance, he wrote his own PNG decoder[0] live on stream, with hardly any prior knowledge of the spec, even though I'm confident that under normal circumstances he'd just use stb_image. I'm sure he did this just to show how you'd go about doing that sort of thing. [0] He only implemented the parts necessary to load a non-progressive 24bit color image, but that still involved writing his own DEFLATE implementation.
- bbatha 4y agoIs Blow even a good example to look at? He's released 2 games in 18 years which definitely had phenomenal game play but are not technically complex even for the the time.
- pron 4y agoMuch of this is very compiler dependent. For example, Java's compiler is generally able to perform more aggressive optimisations, and even virtual calls are often, and even usually, inlined (so if at a particular call-site only one shape is encountered, there won't even be a branch, just straight inline code, and if there are only two or three shapes, the call would compile to a branch; only if there are more, i.e. a "megamorphic" call site will a vtable indirection actually take place). There is no general way of concluding that a virtual call is more or less costly than a branch, but the best approximation is "about the same." Having said that, even Java now encourages programmers to use algebraic data types when "programming in the small", and OOP/encapsulation at module boundaries: https://www.infoq.com/articles/data-oriented-programming-java/ https://www.infoq.com/articles/data-oriented-programming-jav... though not for performance reasons. My point being is that the "best practice" recommendations for mainstream language does change.
- pkolaczk 4y ago> and even virtual calls are often, and even usually, inlined. Last time I checked it could not inline megamorphic call sites, evn if implementations were trivial (returning constants). At the same time I saw C++ compilers able to replace an analogue switch that dispatched to constants with a simple array lookup, with no branching at all.
- pron 4y agoIf I'm not mistaken, C2 inlines up to three targets, but of course, as a JIT, it inlines more aggressively than an AOT compiler, as it does not require a soundness proof.
- pkolaczk 4y agoMost of those Clean code rules are BS. 1. Prefer polymorphism to “if/else” and “switch” - if anything, that makes code less readable, as it hides the dispatch targets. Switch/if is much more direct and explicit. And traditional OOP polymorphism like in C++ or Java makes the code extensible in one particular dimension (types) at the expense of making it non-extensible in another dimension (operations), so there is no net win or loss in that area as well. It is just a different tool, but not better/worse. 2. Code should not know about the internals of objects it’s working with – again, that depends. Hiding the internals behind an interface is good if the complexity of the interface is way lower than the complexity of the internals. In that case the abstraction reduces the cognitive load, because you don't have to learn the internal implementation. However, the total complexity of the system modelled like that is larger, and if you introduce too many indirection levels in too many places, or if the complexity of the interfaces/abstractions is not much smaller than the complexity they hide, then the project soon becomes an overengineered mess like FizzBuzz Enterprise. 3. Functions should be small – that's quite subjective, and also depends on the complexity of the functions. A flat (not nested) function can be large without causing issues. Also going another extreme is not good either – thousands of one-liners can be also extremely hard to read. 4. Functions should do one thing – "one thing" is not well defined; and functions have fractal nature - they appear do more things the more closely you inspect them. This rule can be used to justify splitting any function. 5. “DRY” - Don’t Repeat Yourself – this one is pretty good, as long as one doesn't do DRY by just matching accidentally similar code (e.g. in tests).
- smcl 4y agoI think if someone takes any of these typical guidelines - clean, SOLID, REST etc - and mindlessly applies it they’re likely to end up with a few parts of their applications which look weird, or perform poorly or end up being worse somehow. This is because there are inevitably going to be situations where the guidelines don’t fit well - they’re not necessarily hard and fast rules after all. Any time you have these common rules of thumb in any part of your life you need to evaluate whether or not they are appropriate. But just because they’re not infallibly universal, doesn’t mean they’re wrong, it just means life throws complex situations at us sometimes, and that we need to be pragmatic and flexible
- 4y ago
- sebstefan 4y agoI've seen "Composition over inheritance" more times than I've seen "Polymorphism is good"
- helpfulmandrill 4y agoI take issue with the idea that maintainable code is about "making programmers' lives easier", rather than "making code that is correct and is easy to keep correct as it evolves". Correctness matters for the user - indeed, sometimes it is a matter of life and death.
- Tomis02 4y ago> I take issue with the idea that maintainable code is about "making programmers' lives easier" He's talking about "clean code", not maintainable code. The claim that "clean code" is more maintainable is an unproven assertion. Whenever I interact with a "clean code" codebase, it is worse in every way compared to the corresponding "non-clean" version, including in terms of correctness.
- helpfulmandrill 4y agoDifficult to prove. I'm no OOP evangelist, but the "clean" version in the video looks clearer to me.
- mtrower 4y agoCould it be an issue of experience perhaps? (As in, different experience bases, not overall quantity of experience). Do you have much background in procedural or data-driven programming?
- unconed 4y agoThe example of using shape area seems like a poor choice. First off, the number of problems where having an analytical measure of shape area is important is pretty small by itself. Second, if you do need to calculate area of arbitrary shapes, then limiting yourself to formulas of the type `width * height * constant` is just not going to cut it. And this is where the entire optimization exercise eventually leads: to build a table of precomputed areas for affinely transformed outlines. Throw in an arbitrary polygon, and now it has to be O(n). Throw in a bezier outline and now you need to tesselate or integrate numerically. What this article really shows is actually what I call the curse of computer graphics: if you limit your use cases to a very specific subset, you can get seemingly enormous performance gains from it. But just a single use case, not even that exotic, can wreck the entire effort and demand a much more complex solution which may perform 10x worse. Example: you want to draw lines? Easy, two triangles! Unless you need corners to look good, with bevels or rounded joins, and every pixel to only be painted once. Game devs like to pride themselves on their performance chops, but often this is a case of, if not the wrong abstraction, at least too bespoke an abstraction to allow future reuse irrespective of the use case. This leads to a lot of dickswinging over code that is, after sufficient encounters with the real world, and sufficient iterations, horrible to maintain and use as a foundation. So caveat emptor. Framing this as a case of clean vs messy code misses the reason people try to abstract in the first place. OO and classes have issues, but performance is not the most important one at all.
- Olreich 4y agoTo summarize: if you make the problem complex enough, then the specific performance methods used in the article don’t work. But hey, why not take your complex example? If you apply a polymorphic approach to Bézier curves and polygons, then you still get a 1.5x slowdown compared to a switch statement. If you have any commonality between your implementations of area for them, it’s harder to find, which could be worth 2x or more. If there is a common algorithm for calculating all polygons and curves, wasting work for simple shapes, but vastly improving cache and predictor performance, then you could be leaving another 5x on the table. Orienting the code around operations instead of types is still a win for performance with similar cognitive load compared to type hierarchies. You’re right that once you’ve done the 15x performance gain that Casey demonstrates, the code is pretty brittle and prone to maintenance problems if the requirements change a lot. But I think we can have our cake and eat it too by maintaining our code with the simple path available to fall back to if we get a complicated new requirement. Need to add in complex cases that weren’t thought of before? Add them to new switch cases that are slower, and then keep looking for more performant ways of calculating things of need be.
- winkelwagen 4y agoThis is the first video I’ve seen by him. I’m by no means a fan of clean code. But I think he’s making a fool of himself here. Picking out 1 code example from the book doesn’t proof that much on its own. This stuff is so language, os, hardware and compiler specific anyway. The iPhone comparisons are extremely cringe. Real application do so much more then this contrived example. Something that feels fast isn’t the same thing as something is fast. Would I advise beginner programmer’s to read this book? Sure, let them think about ways to structure code. If he just had concluded with, that it is important to optimize for the right thing that would be fine. But he seems more interested in picking a fight with clean code. And yes performance is a lost art in programming
- jackmott 4y ago[dead]
- randomdata 4y ago> But he seems more interested in picking a fight with clean code. Or, more likely, a straw man. "Clean" exists to provide some solutions to certain problems in TDD. Namely how to separate your logic so that units can be reasonably put under test without an exploding test surface and to address environments which are prohibitively recreated. If you don't practice TDD, "clean" isn't terribly relevant. As far as I am aware, it has always been understood that hard-to-test code has always had some potential to be more efficient, both computationally and with respect to sheer programmer output, but with the tradeoff that it is much harder to test. It is useful to challenge existing ideas, but he didn't even try to broach the problem "clean" purports to solves. Quite bizarre.
- captainmuon 4y agoAlready in his first example, where he says he doesn't use range-based for in order to help the compiler and get a charitable result, he doesn't get the point, I think. You write code in a certain way in order to be able to use abstractions like range-based for, or functional style. If you are hand-unrolling the loop, or using a switch statement instead of polymorphism, you loose the ability to use that abstraction. Esentially the whole point of object orientation is to enable polymorphism without having big switch statements at each call site. (That, and encapsulation, and nice method call syntax.) When people dislike object orientation, it's often because they don't get or at least don't like polymorphism. Most people, most of the time, don't have to think about stuff like cache coherency. It is way more important to think about algorithmic complexity, and correctness. And then, if you find your code is too slow, and after profiling, you can think about inlining stuff or using structs-of-arrays instead of arrays-of-structs and so on.
- Jach 4y agoPerhaps some biases can be excused by committing to C++. Using dynamic dispatch in that language seems to be slow when it's pretty much always some vtable lookups under the hood, but it doesn't have to be that way. Implementations of other languages like Smalltalk or Common Lisp automatically apply (or have as options to specify) various strategies to make convenient abstractions a lot more performant. In Java Land the JVM can do many impressive things -- and interestingly, more numbers of smaller size methods helps, as it tends to "give up" if a function is a giant sprawling mess. A fun story from https://snakeisland.com/aplhiperf.pdf https://snakeisland.com/aplhiperf.pdf on the utility of people using standard inner product / matrix multiplication operators, instead of hard-coding their own loops or whatever: > In the late 1970’s, I was manager of the APL development department at I.P. Sharp Associates Limited. A number of users of our system were concerned about the performance of the ∨.∧ inner product on large Boolean arrays in graph computations. I realized that a permuted loop order would permit vectorization of the Boolean calculations, even on a non-vector machine. David Allen implemented the algorithm and obtained a thousand-fold speedup factor on the problem. This made all Boolean matrix products immediately practical in APL, and our user (and many others) went away very happy. > What made things even better was that the work had benefit for all inner products, not just the Boolean ones. The standard +.× now ran 2.5—3 times faster than Fortran. The cost of inner products which required type conversion of the left argument ran considerably faster, because those elements were only fetched once, rather than N times. All array accesses were now stride one, which improved cache hit ratios, and so on. So, rather than merely speeding up one library subroutine, we sped up a whole family of hundreds of such routines (even those that had never been used yet!), with no more effort than would have been required for one. Or, just look at SQL. I sure appreciate not having to write explicit loops querying the correct indexes every time I want to access some data.
- yarg 4y agoHe's picking holes in example code. Example code will often tell you how to do a thing, and not why to do that thing. And he argues that it's not a straw man.
- cma 4y agoIf he made up his own example it would definitely be called a straw man.
- tialaramex 4y agoMalicious compliance for C++ programmers. This is the person who thinks they're clever for breaking stuff because "You didn't say not to". Managing them out of your team is likely to be the biggest productivity boost you can achieve. In the process of "improving" the performance of their arbitrary benchmark they make the system into an unmaintainable mess. They can persuade themselves it's still fine because this is only a toy example, but notice how e.g. squares grow a distinct height and width early in this work which could get out of sync even though that's not what a "square" is? What's that for? It made it easier to write their messy "more performant" code. But they're not done, when they "imagine" that somehow the program now needs to add exactly a feature which they can implement easily with their spaghetti, they present it as "giving the benefit of the doubt" to call two virtual functions via multiple indirection but in fact they've made performance substantially worse compared to the single case that clean code would actually insist on here. There are two options here, one is this person hasn't the faintest idea what they're doing, don't let them anywhere near anything performance sensitive, or - perhaps worse - they know exactly what they're doing and they intentionally made this worse, in which case that advice should be even stronger. Since we're talking about clean code here, a more useful example would be what happens if I add two more shapes, let's say "Lozenge w/ adjustable curve radius" and "Hollow Box" ? Alas, the tables are now completely useless, so the "performant" code needs to be substantially rewritten, but the original Clean style suits such a change just fine, demonstrating why this style exists. Most of us work in an environment where surprising - even astonishing - customer requirements are often discovered during development and maintenance. All those "Myths programmers believe about..." lists are going to hit you sooner or later. As a result it's very difficult to design software in a way that can accommodate new information rather than needing a rewrite, and yet since developing software is so expensive that's a necessary goal. Clean coding reduces the chance that when you say "Customer said this is exactly what they want, except they need a Lozenge" the engineers start weeping because they've never imagined the shape might be a lozenge and so they hard coded this "it's just a table" philosophy and now much of the software must be rewritten. Ultimately, rather than "Write code in this style I like, I promise it will go fast" which is what you see here, and from numerous other practitioners in this space, focus more on data structures and algorithms. You can throw away a lot more than a factor of twenty performance from having code that ends up N^3 when it only needed to be N log N or that ends up cache thrashing when it needn't. One good thing in this video: They do at least measure. Measure three times, mark twice, cut only once. The engineering effort to actually make the cut is considerable, don't waste that effort by guessing what needs changing, measure.
- KyeRussell 4y agoDavid Farley’s new book is good. It advocates for the tenants of “clean code” (at least in all lowercase), but given his background I trust that he knows how to balance performance and code hygiene. There are people that are wrong on both extremes, obviously. I’ve worked with one too many people that quite clearly have a deficient understanding of software patterns and try to pass it off as being contrarian speed freaks. Just as I’ve worked with architecture astronauts. I’m particularly skeptical of YouTubers that fall so strongly on this side of the argument because there’s a glut of “educators” out there that haven’t done anything more than self-guided toy projects or work on startups whose codebase doesn’t need to last more than a few years. Not to say that this guy falls into those two buckets. I honestly don’t think I know him at all, and I’m bad with names. So I’m totally prepared for someone to come in and throw his credentials in my face. I can only have so much professional respect for someone that is this…dramatic about something though.
- runevault 4y agoHe's a gamedev by trade who also used to work at RAD tools back in the day. You can argue he doesn't know how to work in large teams because I don't think he has, but when it comes to understanding CPUs (or GPUs) as well as a dev can he's about as capable as anyone on that front. He is very much aggressive to a degree I am not a fan of, but when it comes to calling out bad practices, I find him more right than wrong. But he is terrible at delivering his message in a way that won't ensure anyone who didn't agree with him already will get pissed off.
- vborovikov 4y agoWhat is the author suggesting? To write software using infinite loops changing global state? Makes sense for video games but not for the custom enterprise software where clean code practices are usually applied. The enterprise code must be easy to change because it deals with the external data sources and devices, integration into human processes, and constantly changing end-user needs. Clean code practices allow that, it's not about CPU performance and memory optimizations at all.
- Johanx64 4y ago>The enterprise code must be easy to change because it deals with the external data sources and devices, integration into human processes, and constantly changing end-user needs. Clean code practices allow that, it's not about CPU performance and memory optimizations at all. There are no good metrics that measure how "clean code" (atleast the given rules) make the code easier or harder to change and maintain. All the Java style "enterprise type code" from my experience is bloated, full of boilerplate getters and setters and all sorts of abstractions that often make things harder and not easier to understand/maintain, etc. However CPU performance is easy to measure, and sticking to "clean code" rules as given in the video demonstrably sets you back a decade in hardware progress/makes the code run 10x slower. > Clean code practices allow that This is what you believe, not something you can actually measure as far as I know
- favorited 4y agoI work in IT, and I don't think I've ever used an "enterprise" software product and thought to myself, "hey, this is pretty responsive!"
- ctx_matters 4y agoAs always in programming discussions - context matters. https://trolololo.xyz/programming-discussions https://trolololo.xyz/programming-discussions
- readthenotes1 4y ago"it is easier to make working code fast than to make fast code work" That was from either the 1960s or the 1970s and I don't know that anything has changed in the human ability to read a mangled mess of someone's premature optimizations. "Make it work. Make it work right. Make it work fast." how to apply the observation above...
- stunpix 4y agoSo he puts polymorphic function calls into enormous loops to simulate a heavy load with a huge amount of data to conclude "we have 20x loss in performance everywhere"? He is either a huge troll or he has a typical fallacy of premature optimization: if we would call this virtual method 1 billion times we will lose hours per day, but if we optimize it will take less than a second! The real situation: a virtual method is called only a few hundred times and is barely visible in profiling tools. No one is working with a huge amount of data in big loops using virtual methods to take every element out of a huge dataset like he is showing. That's a false pre-position he is trying to debunk. Polymorphic classes/structs are used to represent some business logic of applications or structured data with a few hundred such objects that keep some states and a small amount of other data so they are never involved in intensive computations as he shows. In real projects, such "horrible" polymorphic calls never pop up under profiling and usually occupy a fraction of a percent overall.
- MikeCampo 4y agoJust because you haven't been exposed this issue doesn't mean it doesn't exist. "the real situation", "no one", "in real projects", "never pop up"...give me a break lol.
- kaba0 4y agoOne can reasonably well guess/know the expected input sizes to their programs. You ain’t (hopefully) loading your whole database into memory, and unless you are writing a simulation/game engine or another specialized application, your application is unlikely to have a single scorching hot loop, that’s just not how most programs look like. If it is, then you should design for it, which may even mean changing programming languages for that part (e.g. for video codecs not even C et al. cut it, you have to do assembly), but more likely you just use a bit less ergonomic primitive of your language.
- duskwuff 4y ago> e.g. for video codecs not even C et al. cut it, you have to do assembly This is largely inaccurate. Video encoders/decoders are typically written in C, with some use of compiler intrinsics or short inline assembly fragments for particularly "hot" functions.
- deleted 4y ago[deleted]
- MikeCampo 4y agoThat was enjoyable and I'm happy to see it triggering the basement dwellers.
- eithed 4y agoGiven that code in editor is for human consumption I wonder why can't it be restructured for compiler to make it fast. (Or why compiler can't make it fast). After all - you could leave annotations re, for example structs so compiler knows what will be their size, so can optimize for it
- gumby 4y agoThese days the cost of a programmer is probably a lot greater than the cost of execution, so some of these rules ("prefer polymorphism") are likely worth the tradeoff.
- CyberDildonics 4y agoCost of execution to who? If you don't care about the speed of your program when I execute it, we end up with electron based VPN GUIs that have menus that run at a few frames per second or electron based disk formatters that are a 400 MB download to ultimately run a command line process. If you don't care about execution speed, I don't want to use it.
- gumby 4y agoThe large number of electron apps demonstrates that most companies don’t really care about the customer. But even on the back end, companies seem willing to scale cloud costs rather than make the lumpy and “risky” investment in hiring. I don’t agree with it but I see it everywhere.
- jupp0r 4y agoThere's a tradeoff. Engineering time is expensive. Machine time can be expensive too. We need to optimize these costs by making most code that's not performance relevant easy to read and then optimize performance critical code paths while hiding optimization complexity behind abstractions. Either extreme is not helpful as a blanket method.
- gwbas1c 4y agoTwo years ago I was subjected to NDepend: A clean-code checker and enforcer. Their tool was so dog slow I could see it paint the screen on a modern computer. I rejoiced when we yanked it out of our toolchain. Most of the advice that it gave was unambiguously wrong.
- mgaunard 4y agoMost programming tends to decompose doing an operation for each element in a sequence into implementing the operation for a single element and then doing that repeatedly in a loop. This is obviously wrong for performance reasons, as operations tend to have high latency but multiple of them can run in parallel, so many optimizations are possible if you target bandwidth instead. There are many languages (and libraries) that are array-based though, and which translate somewhat better to how number crunching can be done fast, while still offering pleasant high-level interfaces.
- mabbo 4y agoI think the author is taking general advice and applying it to a niche situation. > So by violating the first rule of clean code — which is one of its central tenants — we are able to drop from 35 cycles per shape to 24 cycles per shape Look, most modern software is spending 99.9% of the time waiting for user input, and 0.1% of the time actually calculating something. If you're writing a AAA video game, or high performance calculation software then sure, go crazy, get those improvements. But most of us aren't doing that. Most developers are doing work where the biggest problem is adding the next umpteenth features that Product has planned (but hasn't told us about yet). Clean code optimizes for improving time-to-market for those features, and not for the CPU doing less work.
- drivers99 4y agoIt still matters because in your example it will affect how smoothly the computer responds once it gets the user input.
- xupybd 4y agoBut how much does that matter? If you're scaling to 1000s of users then yes. If you have a GUI for a monthly task that two administrators use, then no. The less something gets used the longer the payback time on the initial development.
- layer8 4y agoYou’re not wrong, but today’s software is so slow/high latency so often, despite incredibly powerful hardware, that as a rule it should absolutely matter.
- datavirtue 4y agoMaybe it's because they used AWS Lambda and API Gateway for the API?
- TeMPOraL 4y ago> If you have a GUI for a monthly task that two administrators use, then no. Fine, but be honest with yourself and admit that you are contributing a lot to making the lives of those two admins miserable. It doesn't matter if I'm using your software once a month, or once a day. If it's anything like typical modern software, it will make me hate the task I'm doing, and hate you for making it painful. In fact, shitty performance may be the very reason I'm using it monthly instead of daily - because I reorganized my whole workflow around minimizing the frequency and amount of time I have to spend using your software.
- ysavir 4y agoAs a general rule, optimize for your bottlenecks. If you have a large sum of I/O and can see the latency tracked and which parts of the code are problematic, optimize those parts for execution speed. If you have frequent code changes with an evolving product, and I/O that doesn't raise concerns, then optimize for code cleanliness. Never reach for a solution before you understand the problem. Once you understand the problem, you won't have to search for a solution; the solution will be right in front of you. Don't put too much stock in articles or arguments that stress solutions to imaginary problems. They aren't meant to help you. Appreciate any decent take-aways you can, make the most of them, but when it comes to your own implementations, start by understanding your own problems, and not any rules, blog titles, or dogmas you've previously come across.
- rolldat777 4y agoI think this is the point. In the example given, if you introduce a sqrt, then the argument becomes much weaker already. The dispatch is comparable to the computation time. I'm reminded of "Latency Numbers Every Programmer Should Know". I actually just had this debate with myself, specifically about shape classes including circles and bezier curves. However, the operation was instead intersections. There was zero performance difference in that case after profiling so I kept the OOP so that the code wasn't full of case statements.
- _dain_ 4y agoIt's a shifting target though. Let's take your example of a program with a lot of I/O. A straightforward way to optimize that is to find a way to reduce the number of I/O operations you do. And once you do that, the bottleneck shifts. You're spending less time in I/O, both in an absolute sense and relative sense. So you might run into a new non-I/O new bottleneck that was just drowned out in the noise before. So you optimize that ... And sometimes this goes on for many iteration cycles and you end up with a 100-1000x performance improvement.
- t43562 4y agoOne 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 individual is only concerned about the complexity of the piece they're adding rather than the whole thing. That lets lots of people work on adding shapes simultaneously without having to work on the same source files. If you don't need this then what would be the point of doing it? Only fear that you might need it later. That's the whole problem with designing things - you don't always know what future will require.
- gnuvince 4y agoThat'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 new function.
- t43562 4y agoIf 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.
- adam_arthur 4y agoMost performance optimized code can be abstracted such that it reads cleanly, regardless of how low level the internals get. This is the entire premise of the Rust compiler/"zero cost abstractions". Or how numpy is vectorized under the hood. No need for the user to be exposed to this. Writing "poor code" to make perf gains is largely unnecessary. Though there are certainly micro-optimizations that can be made by avoiding specific types of abstractions. The lower level the code, the more variable naming/function encapsulation (which gets inlined), is needed for the code to read cleanly. Most scientific computing/algorithmic code is written very unreadably, needlessly.
- totorovirus 4y agoI think juniors should write clean code until they can negotiate the technical debt against the speed
- boredumb 4y agoI've seen clean code lead to over-architected and unmaintainable nightmares that ended up with incorrect abstractions that became much more of a problem than performance. The more the years pile up the more I agree with the sentiment in this post, generally going for something that works and is as optimal of code as I would get if I was to come back to make it more performance oriented in the future, I end up with something generally as simple as I can get. Languages and libs are generally abstract enough in most cases and any extra design patterning and abstracting is generally going to bite you in the ass more than it's going to save you from business folks coming in with unknown features. I suppose, write code that is conscious of its memory and CPU footprint and avoid trying to guess what features may or may not reuse what parts from your existing routines, and try even harder to avoid writing abstractions that are based on your guesses of the future.
- commandlinefan 4y ago> I've seen clean code lead to over-architected and unmaintainable nightmares I agree, but surely you wouldn't really say that his end-state in this particular article is more maintainable than the starting point.
- gnuvince 4y agoThe table version also has something really interesting going for it: it just begs to be thrown out and replaced should new requirements come in that don't fit with that design. If a new type of shape comes in that doesn't fit with the factor * width * height model (e.g., a trapezoid), we'd need to go back to the drawing board and figure out how to make the existing and new cases work harmoniously together. On the other hand, an abstract base class broadcasts the message that we should fold our new cases into the existing design -- that's what base classes are made for. But even when new cases don't fit neatly in the existing design, we often feel as programmers that we need to pay respect and deference to existing design, especially if it was made with extensibility in mind. And so we add more complexity (maybe we need an extra field or an extra method), make more kludges, and soon enough the original OO design is a mountain of complexity and has so much "gravity" that it's nearly impossible to escape it anymore -- nobody can imagine throwing it out and starting fresh, so it just keeps gaining complexity. And for all its complexity, it's also slower.
- nimih 4y ago> Our job is to write programs that run well on the hardware that we are given. 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. No one (that I’ve ever spoken to or worked with, at least) is under any illusions that you can almost always trade off maintainability for performance. Admittedly, I think a lot of prescriptions that get made in service of “clean code” are silly or counterproductive, and people who obsess over it as an end unto itself can sometimes be tedious and annoying, but this article is written in such incredible bad faith that its impossible to take seriously.
- loup-vaillant 4y ago> 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 Philosophy of Software Design) instead.
- berkes 4y ago> it failed. That is a large statement you make there. It begs for backing up.
- loup-vaillant 4y agoBob 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.
- glintik 4y agoMost horrible mistake is not in the list: immutability.
- DeathArrow 4y agoWhy is immutability a mistake?
- glintik 4y agoSignificant performance and memory penalties.
- xupybd 4y ago>It simply cannot be the case that we're willing to give up a decade or more of hardware performance just to make programmers’ lives a little bit easier. Our job is to write programs that run well on the hardware that we are given. If this is how bad these rules cause software to perform, they simply aren't acceptable. That is not our job! Our job is to solve business problems within the constraints we are given. No one cares how well it runs on the hardware we're given. They care if it solves the business problem. Look at Bitcoin, it burns hardware time as a proof of work. That solves a business problem. Some programmers work in industries where performance is key but I'd bet not most. CPU cycles are much cheaper than developer wages.
- dgb23 4y agoWell yes it kind of is? Everyone solves problems. We solve problems with computers. And we use them because they’re autonomous, remember exact details and are very fast and reliable. There’s of course some level of good enough. We don’t write ad-hoc scripts in assembly. But to say dev time is more expensive than computer time only makes sense if programs are actually fast. Fast, reliable feedback loops matter. Consistency matters. And simplicity matters in many dimensions. Web application servers that are orders of magnitude (N times) slower than they should be (not even _could_ be) cost us N times more hardware resources, N times more architectural complexity that require specialized workers and tools and so on. Speed and throughout matter for productivity. Not just ours but our user’s as well. Good performance is important for good UX. Wasting fewer cycles opens up opportunities to do meaningful things.
- danuker 4y ago> and are very fast and reliable. Among the most popular languages is Python. It is popular in spite of its bad performance, high memory use, and lack of CPU multithreading. And it is heavily ran on servers. Why? Because running Python apps is still much cheaper than hiring humans to wait for calls or manage e-mails. Humans are valuable. They should not be working on easily automatable problems. The bottleneck is automating AT ALL, rather than automating with a low machine cost. Only at huge scale (i.e. Big Tech with billions of daily events) does it warrant to optimize the code. Of course, assuming you have a sane computational complexity. If you don't, it doesn't matter which paradigm you use.
- softfalcon 4y agoIn my personal opinion, this is less of an argument of "clean code" vs "performant code" and it seems to be more of traditional "object oriented programming" vs "data driven design". Ultimately though, data driven design can fit under OOP (object orient programming) as well, since it's pretty much lightweight, memory conforming structs being consumed by service classes instead of polymorphing everything into a massive cascade of inherited classes. The article makes a good argument against traditional 1980-90's era object oriented programming concepts where everything is a bloated, monolith class with endless inheritances, but that pattern isn't extremely common in most systems I've used recently. Which, to me, makes this feel a lot like a straw man argument, you're arguing against an incredibly out-dated "clean code" paradigm that isn't popular or common with experienced OOP developers. One only really has to look at Unity's data driven pipelines, Unreal's rendering services, and various other game engine examples that show clean code OOP can and does live alongside performant data-driven services in not only C++ but also C#. Hell, I'm even doing it in Typescript using consolidated, optimized services to cache expensive web requests across huge relational data. The only classes that exist are for data models and request contexts, the rest is services processing streams of data in and out of db/caches. If there is one take-away that this article validated for me though, it's that data-driven design trumps most other patterns when performance is key.
- flavius29663 4y agoOne of the the points of "clean code" is to make it easy to find the hotspots and optimize those. Write the codebase at a very high level, plenty of abstractions etc. and then optimize the 1% that really needs it. Optimizing a small piece of software is not going again clean code, on the contrary, it re-enforces it: you can spend the time to optimize only what is necessary.
- TeMPOraL 4y agoOne of the main points of Casey's videos is that following the "clean code" mantras will make your code unoptimizable. You may delay performance considerations until the end, then fire your profiler, find that 1% that really needs it[0], ... and realize that you can't get more than 1.5x - 2x speedup without ripping out the core 10% of the codebase and rethinking it properly. Were you, however, to consider performance from the start, that 10% would've been designed around completely different abstractions, and already 10x faster in the unoptimized version. "Clean Code" should be called pessimistic coding - a big part of it is to enable OK flexibility in any imaginable direction. But real-life code will not change in all possible direction - in fact, you can predict quite well roughly what can and cannot change. Writing for performance means, among other things, making things easier both for human and the CPU by reducing flexibility in the unlikely directions. In the toy example from the video: Casey's proposed alternatives baked in the assumption that the program is working with shapes which, for a given computation, all can fit a specific family of equations. Clean code will make it just as easy to add a square as to add a parametric spline surface. Casey's code will make the former trivial, the latter hard without redoing the entire shape-related code. It's a good tradeoff if you're making a program that mostly works with non-parametric simple polygons, because nobody will need parametric splines in it. On the off chance they will, they can pay for the extra effort - and in the meantime, your software is 20x faster than the equivalent "clean code" version. -- [0] - This thinking alone is a problem. It's not the 1% that needs some optimization work. The entire user-interacting surface and everything downstream of it need it, which means effectively the entire program. You're free to set a cut-off point beyond which you don't care about "less important" features - but 1% seems quite too early.
- whstl 4y ago"One of the the points of "clean code" is to make it easy to find the hotspots and optimize those" Where is this in the book? I can't find a single mention of "hotspot" in the book, and even "optimizations" only shows up 3 times.
- scotty79 4y agoPersonally I think we'd be in a better spot today if instead of class hierarchies and polymorphism the thing that would go mainstream was Entity-Component-System approach with composition instead of inheritance.
- pshirshov 4y agoIn 98% of the cases there is no difference between O(N) and O(N/4)
- garganzol 4y agoThe article gives an advice from the past. Nowadays it is all about zero-cost abstractions and automatic optimization. These trends will only solidify in the future defining the new norm. And until that future fully arrives, optimize for your bottlenecks.
- andix 4y agoDon’t optimize early. 99% of code doesn’t have to be fast, it has to be right. And as code needs to be maintained, it also needs to be easy to read/change, so it stays right. You shouldn’t do things that make your code utterly slow though.
- 0xbadcafebee 4y ago"Clean" is a poor descriptor for source code. The word "clean" in reference to software is simply an indicator of "goodness" or "a pleasant aesthetic", as opposed to the opposite word "dirty", which we associate with undesireable features or poor health (that being another misnomer, that dirty things are unhealthy, or that clean things are healthy; neither are strictly true). "Clean" is not being used to describe a specific quality; instead it's merely "a feeling". Rather than call code "clean" or "dirty", we should use a more specific and measurable descriptor that can actually be met, like "quality", "best practice", "code as documentation", "high abstraction", "low complexity", etc. You can tell when something meets that criteria. But what counts as "clean" to one person may not to another, and it doesn't actually mean the end result will be better. "Clean" has already been abandoned when talking about other things, like STDs. "Clean" vs "Dirty" in that context implies a moral judgement on people who have STDs or don't, when in fact having an STD is often not a choice at all. By using more specific terms like "positive", or simply describing what specific STDs one has, the abstract moral judgement and unhelpful "feeling" is removed, and replaced with objective facts.
- rudolph9 4y ago> We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3% https://dl.acm.org/doi/10.1145/356635.356640 https://dl.acm.org/doi/10.1145/356635.356640 The author of the post fails to articulate how we strike a healthy balance and instead comes up with contrived examples to prove points that only really apply to contrived examples.
- Tomis02 4y agoYou managed to misunderstand both Casey Muratori and Donald Knuth. You are not alone, the majority of the industry seems to have gotten it wrong. Casey tells you that following "Clean Code" can give you a huge performance hit for no obvious benefit. And even if "Clean Code" were to be more maintainable (it's not; in my experience, it's actually worse for maintainability), you should still be extremely aware of the cost you're likely to pay down the track. It's not a contrived example, it's literally textbook "Clean Code". I'll say it again: "Clean Code" gives you slower, less maintainable code, and you get nothing from it. Maybe you can afford it, maybe in your use case it's not a big deal, but you should be informed. Knuth tells you to measure before optimizing, which Casey did. Knuth does NOT tell you "don't worry about performance, you'll optimize later". You quoted Knuth but stopped right before the best part: > A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; BUT ONLY AFTER THAT CODE HAS BEEN IDENTIFIED [emphasis mine]. It is often a mistake to make a priori judgments about what parts of a program are really critical, since the universal experience of programmers who have been using measurement tools has been that their intuitive guesses fail. To recap: "A good programmer will not be lulled into complacency by such reasoning" - in other words, just because 97% of the code may not need optimization does NOT mean you should not be thinking about performance. Knuth's point is that when identifying hotspots, programmers were relying on intuition rather than measurement. That's what he meant by "premature optimization". Knuth did not mean (especially since it was the 70s) you should write "Clean Code" that you know has worse performance for little benefit. And Knuth does not write "Clean Code", by the way. > The author of the post fails to articulate how we strike a healthy balance There is no healthy balance between a good idea and a bad idea. Just eliminate the bad idea.
- Pr0ject217 4y agoCasey brings another perspective. It's great.
- zX41ZdbW 4y agoYou don't have to give up clean code to achieve high performance. The complexity can be isolated and contained. For example, take a look at ClickHouse codebase: https://github.com/ClickHouse/ClickHouse/ https://github.com/ClickHouse/ClickHouse/ There is all sort of things: leaky abstractions, specializations for optimistic fast paths, dispatching on algorithms based on data distribution, runtime CPU dispatching, etc. Video: https://www.youtube.com/watch?v=ZOZQCQEtrz8 https://www.youtube.com/watch?v=ZOZQCQEtrz8 But: it has clean interfaces, virtual calls, factories... And, most importantly - a lot of code comments. And when you have a horrible piece of complexity, it can be isolated into a single file and will annoy you only when you need to edit that part of the project. Disclaimer. I'm promoting ClickHouse because it deserves that.
- kingcai 4y agoI like this post a lot, even if it's a somewhat contrived example. In particular I like his point about switch statements making it easier to pulled out shared logic vs. polymorphic code. There's so much emphasis on writing "clean" code (rightly so) that it's nice to hear an opposing viewpoint. I think it's a good reminder to not be dogmatic and that there are many ways to solve a problem, each with their own pros/cons. It's our job to find the best way.
- lumb63 4y agoI don’t understand why there is still the false dichotomy between performance and speed of development/readability. Arguments on HN and in other software circles suggest performant code cannot be well organized, and that well organized code cannot be performant. That’s false. In my experience, writing the code with readability, ease of maintenance, and performance all in mind gets you 90% of each of the benefits you’d have gotten focusing on only one of the above. For instance, maybe instead of pretending that an O(n^2) algorithm is any “cleaner” than an O(n log n) algorithm because it was easier for you to write, maybe just use the better algorithm. Or, instead of pretending Python is more readable or easier to develop in than Rust (assuming developers are skilled in both), just write it in Rust. Or, instead of pretending that you had to write raw assembly to eke out the last drop of performance in your function, maybe target the giant mess elsewhere in your application where 80% of the time is spent. A lot of the “clean” vs “fast” argument is, as I’ve said above, pretending. People on both sides pretend you cannot have both, ever, when in actuality you can have almost all of what is desired in 95% of cases.
- berkes 4y agoI'd even go so far as to say that "clean" code is a requirement for performance optimization. For a loose definition of clean. Code that is unreadable, tightly coupled, untestable or just messy is much, much harder to work in than code that is readable, loosely coupled, well-tested and clean. This has been proven often and is really a no-brainer. Performance-optimizing is finding the bottleneck, then rewriting that without changing the functional behaviour. For this you need resp. readability (to find bottlenecks you must be able to understand flow and code), ability to rewrite (tightly coupled code cannot be rewritten in isolation) and insurance the behaviour doesn't change (test coverage). Ergo: a clean archictecture is a requirement to make code more performant in the first place. Even if that architecture is bad for performance in itself, it enables future improvements.
- whstl 4y agoI find it funny that people keep throwing non-caps "clean" code here in this post without knowing the context, you now have "clean architecture" too, and it made it more difficult to know what you're talking about. Clean Code is actually a book by Uncle Bob. And Clean Architecture is the name of another book by him. What Casey is criticizing isn't "good code". He's criticizing Uncle Bob's philosophy.
- panny 4y agoMaintainable code or performant code, yes. That's always a tradeoff. The most high performance code will be manually tuned assembly, but I don't see author writing in assembly, so he's already made some tradeoff against performance. It's all down to your priorities.
- GrumpySloth 4y agoThis thread is, predictably, another demonstration of conflating optimisation with being aware of performance. The presented transformation of code away from “clean” code had nothing to do with optimisation. In fact, it made the code more readable IMO. Then it demonstrated that most of those “clean” code commandments are detrimental to performance. So obviously when people saw the word “performance”, they immediately jumped “omg, you’re optimising, stop immediately!” Another irritating reaction here is the straw man of optimising every last instruction: the course so far has been about demonstrating how much performance there even is on the table with reasonable code, to build up an intuition about what orders of magnitude are even possible. Casey repeated several times that what level of performance is right for your situation will depend on you and your situation. But you should be aware of what’s possible, about the multipliers you get from all those decisions. And of course people bring up profilers: no profiler will tell you whether a function is optimal or not — only what portion of runtime is spent where. And if all your life you’ve been programming in Python, then your intuition about performance often is on the level of “well I guess in C it could be 5-10 times faster; I’ll focus on something else”, which always comes up in response to complaints about Python. Not even close.
- loup-vaillant 4y agoAgreed, but most people here haven't paid for the rest of the course, and Casey sometimes forgot that here he's addressing a wider audience. For instance he fails to explain why he didn't bother addressing the tail of his unrolled loop. He does in the course, but here he's just assuming it's irrelevant, and doesn't address again potential criticism like "he doesn't even bother to write correct code, look at that lazy unrolling!". Thankfully there's a free lecture that explains the broad concept with an example. It's this lecture that convinced me to try out his course, where I hope he'll go into more details: https://www.youtube.com/watch?v=pgoetgxecw8&list=PLEMXAbCVnmY4JbNByvpgEzWsLRKVaF_pk&index=7 https://www.youtube.com/watch?v=pgoetgxecw8&list=PLEMXAbCVnm...
- hinkley 4y ago>what level of performance is right for your situation will depend on you and your situation A lot of my performance and code quality chops came from projects where the team was comfortable with the performance of the system but the business was not. They wanted to stop when it was right for them but not right for the situation. It ended up negatively affecting my opinion of them because ultimately I started to see it as deflecting. It's fine because I don't know what else to do, not because this is the best that can be done.
- allmadhare 4y agoMaintainability and performance are often at odds, but that doesn't mean you should throw out one for the other in every case, and I don't think that's what people like Robert C. Martin were ever intending with Clean Code. It's like database denormalization, it may violate normalization principals but it when applied to a well designed database is a valid optimization technique when done with proper understanding of the implications of said optimizations. More importantly though, we are willing to sacrifice raw performance for developer experience and higher maintainability because developer time is expensive, and most stakeholders would prefer that you can add feature xyz in a reasonable time, over feature xyz running marginally faster. If ease of development and maintenance weren't important, we'd just write everything in assembly and bypass all these abstractions altogether.
- Mizoguchi 4y agoThe problem with this is that in most real world scenarios it is much cheaper to add more hardware resources to slow performing apps than hiring, training and retaining programmers to learn, debug, enhance and maintain poorly written code, particularly when the useful life of many software solutions can last decades and hardware becomes cheaper every year.
- Octokiddie 4y ago> So by violating the first rule of clean code — which is one of its central tenants — we are able to drop from 35 cycles per shape to 24 cycles per shape, impling that code following that rule number is 1.5x slower than code that doesn’t. To put that in in hardware terms, it would be like taking an iPhone 14 Pro Max and reducing it to an iPhone 11 Pro Max. It's three or four years of hardware evolution erased because somebody said to use polymorphism instead of switch statements. The benchmark is a tight loop where the vtable lookup is a big chunk of the total computation. I don't think one can extrapolate this 1.5x improvement to real code. If anything, it represents an upper bound on the performance improvement you might expect to see. I also didn't see anything about how the code was compiled. Various optimizations could affect performance in meaningful ways.
- loup-vaillant 4y ago> The benchmark is a tight loop where the vtable lookup is a big chunk of the total computation. I don't think one can extrapolate this 1.5x improvement to real code. No you can't. But other things get worse at a bigger scale. Not all programs make those virtual calls absolutely everywhere so the overhead scales with the program, but many don't pay attention to memory access pattern, and cause their instruction pointer to jump all over the place and trash their instruction cache. Mike Acton have once shown that merely reordering objects by types, while keeping those virtual calls, can help the instruction cache quite a bit just by making sure the same code was called several times in a raw.
- oakpond 4y agoI'm sympathetic to the rebellion against 'clean code'. I think this obsession with clean code is a natural reaction to the overwhelming number of gotchas that seem to just come with the job. It's a bit like when a parent watches their kid get hurt outside the house and in a complete overreaction locks the kid in the house for life. Thing is, if I'm programming an airplane control system, I very much want that kind of pedantry I think. I really don't want to make a single mistake writing that kind of program. If I'm programming a video game, just let me write the code that I want. Nobody's going to die if it blows up in my face. I'm not sure what should be the lesson from all this... Perhaps don't pick C++ unless you absolutely need to?
- axilmar 4y agoIn C++, you can have clean code and performance, by utilizing templates. In the example given, all polymorphism can be removed, and the shapes can be stored in std::tuple structure. And then the operations would be faster than C, since no switch statement would be needed.
- adamrezich 4y agothen your compile times balloon even larger than they already are in C++
- maerF0x0 4y agoIf instead of measuring the benchmark of a specific optimized code against a non-optimized code we instead measure the time when the user gets their answer in many cases the non-optimized code will be several months faster. Why? Because it takes time to do optimizations and I can ship the non-optimized sooner. Similarly we can then look at an iterated design and realize the optimized code is frequently going to be harder to refactor or understand (a precondition of refactoring). So now the time to when a customer gets their answer is delayed again. Optimization step comes long after clean code. Clean code is most useful in the first 2 of the typical 3 steps[1] 1. Make it work (iterations of what working even means) 2. Make it right (iterations of what right even means) 3. Make it fast. [1]:https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
- loup-vaillant 4y agoThis video is not optimisation. It's about avoiding needlessly slow pasterns that don't even make it more maintainable or cheaper to produce.
- 29athrowaway 4y agoPremature optimization is often a bad idea.
- tonymet 4y agoI worked at a company where the client devs had a gaming background and the server devs had a web background. The gaming devs were obsessed with framerates and efficiency while server devs wanted to decouple modularize everything. There's no solutions only tradeoffs
- baranoff 4y agoSuch a misguided article. What i constantly fight at work is poorly written unmaintainable code. The code that needs to be fast is 1% or less. Use three step rule when implementing something: 1. Make it work 2. Make it pretty 3. Make it fast (measure and optimize where it matters)
- gardenhedge 4y agoFighting a lost battle unfortunately. Non-technical managers know what clean code is and are able to discuss it
- alfalfasprout 4y agoA lot of people here seem to be saying that it's a spectrum between "clean code" and "performant code". Even the author alludes to that. Or that most code doesn't need to be fast. I find that viewpoint concerning because the reality is this isn't really a dichotomy. Code can be both performant and clean (note clean is not the same as elegant). One thing I think is confusing people is the dogmaticism about what's "idiomatic" is especially bad in OOP-heavy languages. This is especially bad in Java where a fetishization of design patterns have led to codebases which are both ugly and unperformant. The reality is software design needs to consider performance from the get-go. Sure there is such a thing as "premature optimization" but if you've determined performance is a goal then you should follow best practices for high performance from the get-go. That includes not trying to perform math on iterables of objects (since that prevents vectorizatio since the data isn't contiguous), avoiding accumulations, not creating and destroying tons of objects, etc. This can all be done in a clean way! And low level code can be cleanly encapsulated so that other interfaces remain idiomatic and simple. A lot of people fret that this approach leads to "leaky abstractions" because implementation details inform the interface design. That just means you need to iterate on the interface design so it makes sense.
- quickthrower2 4y agoClean code to me is like good writing. It should be easy to read and comprehend later. The rules are not rules but guidelines. I think OO centric rules are harmful in a word where languages support functional programming etc. Polymorphism isn’t always the best answer. A big nested if statement that reads like the business spec can be easier to follow and reason about. That aside if easy to understand code makes your app a bit slower, you profile to work out why and fix up the bits that matter making the tradeoff where it is needed. Writing code for what you think will be performant everywhere, and not caring about readability in the process is a fools errand, at least in most SaaS/Web/Business apps.
- thenoblesunfish 4y agoAnd another similarity to good writing - sometimes you really do need to have really dense, precise stuff that isn't easy to understand without a close read. You put that into an appendix when writing, and into optimized low level routines/libraries when writing software. That way you can make things readable, but still have the "performance".
- jbverschoor 4y agoYep.. I much prefer 2000 lines of code in one function with very little calls. Easier to reason about, less bugs, easier to maintain. It's very easy to indicate when a new part is coming, just enter a big comment block of what you're doing.
- mxmbrb 4y agoThis is the perfect example. You start out like that and all is fine, but two years, a few changes and some bugfixes later your now 2700 line method is a total zombie. The comments blocks are lying and you have incromprehensible dependencies. More over you have to read it all every time you make a change in the method, its inputs or understanding its output. That just screams 'refactor me!'. The exceptions of course are when initializing a lot, or having a ton of bindings, etc. No need to cramp this into sub methods like its a religion. Cramping code may be fine for high performance code where the mandays are justified for the two line code fix. But in most Software you likely just want to include the new cache methods, change the used object, add a button or fix an update. And the person fixing it probabaly hasn't seen the specific code ever before. There is sadly no need for high performance software, when you can't sell it in time.
- Falconerd_ 4y agoReading the comments it seems like a lot of people missed this part. > We can still try to come up with rules of thumb that help keep code organized, easy to maintain, and easy to read. Those aren't bad goals! But these rules ain’t it. They need to stop being said unless they are accompanied by a big old asterisk that says, “and your code will get 15 times slower or more when you do them.” He isn't against organised and maintainable code, he just thinks the current definition isn't worth the trade-off.
- hiccuphippo 4y agoMy one pushback against this is: we have a CDN, no amount of optimizations is gonna beat having the result already in memory and return that.
- tiffanyh 4y agoNo OpenBSD reference? It has extremely clean & easy to discern code. But it’s also not the most performant.
- notShabu 4y ago"How to Produce Code" on a spectrum of efficiency -> abstraction Binary Assembly ... ... C++ ... ... Python ... ... Product Manager speaking with words: "Can you make it have more pizazz?
- larsonnn 4y agoWhen you think, it’s not worth it, Try to imagine when your software not run once, but runs a few thousand times or more per second. So by having some operations exceptional faster you could not only save time also you save energy.
- civilized 4y agoMy biggest confusion with the "clean code" concept is, what does clean mean? Such a vague concept seems to invite arbitrary bikeshedding over how many lines a function should have, whether comments are good, etc. In a kitchen, clean is a pretty objective concept: no dirt or grime, objects put away with similar objects. Not sure what it means in code, but it seems many people have strong, conflicting, subjective opinions about it. Doesn't seem like a good recipe for productivity or alignment. I feel like it would be wiser to limit the concept of clean to the eradication of obviously "dirty" or "cluttered" things, like inconsistent style, or naming a module in a way that is misleading about its contents or functionality. Just as all different kinds of buildings can be clean, a code of "cleanliness" should not be so comprehensively prescriptive about architecture and organization. Use more appropriate names for those dimensions of code quality, rather than "clean" as the single stand-in for every good thing.
- xwdv 4y agoWow, every word in this article was wrong.
- kazinator 4y agoFor all the creeping featuritis that C++ is acquiring like a dirty snowball, doesn't it have a solution for this yet? virtual u32 CornerCount() = 0; you should be able to declare a virtual data member virtual u32 CornerCount; // default value zero how this would be implemented is that it simply goes into the vtable. ptr->CornerCount retrieves the vtable from the object, and CornerCount is found at some offset in that table, just like a virtual function pointer would be. There is no need to pull out a function pointer and jump to it. In C I would do it like this // Every shape has a pointer to its own type's static instance of this: struct shape_ops { unsigned (*area)(struct shape *); unsigned corner_count; } // get_area looks like this: unsigned shape_area(struct shape *s) { return s->ops->area(s); } // the corner count isn't calculated so it's just unsigned shape_corner_count(struct shape *s) { return s->ops->corner_count; } Everyone can override corner_count with their value. What you can't do is implement a calculation which determines the corner count dynamically, but that can be a reasonable constraint.
- gyy52380 4y agoThere is only one vtable object per base class; all shapes share the same vtable pointer and your virtual CornerCount would be shared across all Shape instances. You are describing a potential implementation for class static variables.
- kazinator 4y agoIn plain C we can make virtual function calls faster by forwarding the pointers into the object instance. Say we have this: int obj_api(object *o, char *arg) { return o->ops->api(o, arg); } that's representative of how C++ virtual functions are commonly implemented. It gets more hairy under multiple inheritance and such. It requires several dependent pointer loads. We must access the object to retrieve its ops pointer (the vtable) and then access the vtable to get the pointer to the function, and finally branch there. To call that function a little faster we can go to this: int obj_api(object *o, char *arg) { return o->api(o, arg); } in other words, forward the api function pointer from the static table to the object instance. Ok, so now each time we construct a new object, we must initialize o->api. And the pointer takes up space in each instance. So there is a cost to it. But it blows away one dependent load. And the "clean" structure of the program has not changed; it has the same design with virtual functions and all. We could do this for some select functions that could benefit from being dispatched a little faster. I don't think there is a way in C++ to tell the compiler that we'd like a certain virtual function to be implemented faster, at the cost of taking up more space in the object instance and/or more time at object construction time.
- dym_sh 4y agohey, what if had some kind of optimization step which would take clean code and make it more about performance than maintainability
- charles_f 4y ago> Prefer polymorphism to “if/else” and “switch” Wtf? First time I hear about this one, and it sounds like a dumb dogma. > It’s a base class for a shape with a few specific shapes derived from it: circle, triangle, rectangle, square. We then have a virtual function that computes the area. Quite literally the first and simplest example for why you should prefer composition over inheritance[^1] (that and ducks and chickens). Good strawman. I am unconvinced. 1: https://en.m.wikipedia.org/wiki/Composition_over_inheritance https://en.m.wikipedia.org/wiki/Composition_over_inheritance
- rossjudson 4y agoLots of very correct things said there...except for this: "The more you use the “clean” code methodology, the less a compiler is able to see what you're doing. Everything is in separate translation units, behind virtual function calls, etc. No matter how smart the compiler is, there’s very little it can do with that kind of code." I suppose even that is true, but JIT compilation regularly walks right around those problems. Yes, your code is written to say virtual this, or override that...but the JIT don't care. Is it looking at a monomorphic call site? Or even if it's not, is it ok to think of it as monomorphic right now? Great -- inline away. All that being said...I once got into a readability tiff over the use of a Java enum in a particularly performance sensitive chunk of code. I went with ints so I could be very, very explicit about exactly what I wanted, and the rather large performance gain...and lost. Yay! Your mileage may vary, and your measurements may vary.
- userbinator 4y agoRelated recent discussion about the actual Clean Code book: https://news.ycombinator.com/item?id=34843128 https://news.ycombinator.com/item?id=34843128 I prefer a much simpler rule: if it's easy for the CPU to execute, it's likely easy for you to read too. That means: no deep nesting, minimise branchiness (indirect calls are the worst), keep the code small and simple.
- deleted 4y ago[deleted]
- Dr-NULL 4y agoNot gonna lie, but the first example of using switch instead of polymorphism still looks clean and easy to understand for me.
- ummonk 4y agoJust in terms of readability and maintainability I find polymorphism to be significantly worse than switch-statements. It's hard to locate all the implementations of a particular function and read through them and edit them when they aren't in one place in a single switch statement. Higher performance is merely extra icing on the cake when using switch statements over polymorphism.
- hooby 4y agoObviously, if you are doing performance-critical code in a performance-critical application, you will be doing stuff like inlining and other things that "break" clean-code "rules". I put that into quotes, because to me personally these aren't strict rules - but rather guidelines. And they aren't meant to be pushed to the absolute extreme - but rather be seen as methods/tools used to achieve the actual goal: easily readable, maintainable and modifiable code. And in my workplace, "performance" isn't measured in cpu-cycles, but rather in man-hours needed to create business value. Adding more compute power comes cheaper than needing more man-hours. For the most part, it still seems to be a good idea to train new developers to know and understand clean code. It will help them produce more stable, less buggy and more readable code - and that means the code they write will also be easier to optimize for performance, if necessary. But with my work, that sort of optimization seems only ever necessary for very small pieces of code - most definitely not the entire code base.
- fulafel 4y agoThere are lots of writings about technical and architectural reasons code in games performs better than code in GP applications, but people often forget the top level reason: they have clear performance targets right from the start and performance is the most obvious thing (right after "not crashing") that you see about how well a game works. Everything follows from this. It's not that game devs are so much cleverer than other devs, they are just faced with first hand feedback of "does the game code hit the frametime budget" constantly and the whole dev org is committed to that.
- kgeist 4y agoIn my experience of writing enterprise software, the main offender is N+1 query problem at an API boundary. I.e. when a module/package exposes only a method to process items one-by-one. In case you suddenly want to process 1000 items instead of 5, you'll end up having 1000 separate DB calls, HTTP calls etc. Same applies for gamedev where the author is coming from: a naive renderer could switch shaders individually for every object when you want to sort by shader and switch only a few times. When peformance suffers, you have to change 2+ modules (the client and the server) to support batch operations and a lot of programmers don't have time to do it or simply can't do it because they use a third-party module/service they can't change. Inside a module you can default to clean code or switch to an optimized version if need arises. In a small, well encapsulated/defined module you can write very simple code without overengineered abstractions because it covers a simple model which doesn't need too much abstraction. So my take is write small modules, design abstractions at the API boundary, and always expose batch operations.
- theK 4y agoJust keep in mind that actually keeping that 1.5x or even 10x performance boost you need to apply these consistently to a laaarge code base (performance critical apps tend to be this). This means that in 2-3 months you end up with a codebase that is very difficult to work with, team members tripping over each other due to bad deps and abstractions and your iteration time start shooting up. Doesn't seem like a realistic avenue to choose except maybe when coding to a final spec?
- bandika 4y agoI find it amusing that many corporate dev teams picks C++ for its performance / low levelness, but then reject any code that Casey's advocate for. It is extremely hard to convince them to consider these things (ie in this case cache misses and branch mispredictions). Now, if we consider only a conservative 2x speed-up, I might not care if my app starts up in 2s or 4s, but I do care if my device's battery last for 20h v 10h.
- rullelito 4y agoI work at a FANG with products that have 10M-100M users, and I probably design code for performance < 1% of the time. I suspect this is the norm.
- Ultimatt 4y agoThis problem feels like a no runtime type problem. Highly dynamic languages have tonnes of grim problems like this, that they have to deal with because there is no good type information anywhere so stats at runtime is how you optimise. Raku for example has a lot of specialising runtime optimisations where all the virtual function calls get specialised and become dynamic by exception when the VM executes code.
- noobermin 4y agoThis comment section just shows how so many developers are victims of group think. Here is actual evidence that at least hints that the primary paradigm is wrong, and immediately a bunch of nerds jump and attack, instead of taking the criticism in good faith. Compare this to discussions about FP, new languages like Rust, and so forth. This really demonstrates the primary vogue mindset is increasing complexity and hierarchy to the detriment of all else, and is why the supposed new paradigms of "modern software development" are not really that new but just evolutions of the current paradigms. You really touch what is a culture's sacred cows by that which attracts criticism without any real sincere rebuttal.
- dsattt 4y agoI was thinking the same thing. Everybody is so desperate to ship features fast that any hint against it triggers a mob with a bloodlust.
- WA 4y agoWhat are you talking about? The first comment is literally a real sincere rebuttal: "performance doesn’t matter in 99.9% of apps".
- noobermin 4y agoIf 99% of apps are slower, that's everything I do on a computer! Yes it matters! It doesn't matter for corporate environments perhaps. It does hell of matter for consumer facing web stuff, both the front end and the backend! 99% is a lot of stuff, so yes it does matter. And the top reply (right now) is about 99% of the time is spent waiting for user input, and no, that isn't even true. A lot of that "input" is waiting on the network, and the number of requests for any application makes per unit time definitely scales with increasing code complexity. But anyway, may be that makes a tenuous argument that most code does not care about performance, but again, if it's 99% of code, then yes it matters because it's my entire computer, and that's how we have machines that are faster than they've ever been yet they struggle to edit text compared to say emacs on pentium 4.
- flumpcakes 4y ago
- cranium 4y agoYou can optimize even further by creating a custom chip to compute the area of shapes in the order of billions per second. But what's the point? Where is the value? Can't say it better than Knuth: We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. Clean code has never been about performance, it's about the other people that will read your code – future you included. Performance of people is more valuable for any product than code performance[1]. Only when the performance becomes a bottleneck, or you want to optimize energy efficiency then sure, don't pass that 3% opportunity. [1] I would argue that it still holds for products that need high(er) performance code like video games, embedded systems, particle physics, ... These products just happen to hit bottlenecks way faster and some have hard cutoffs (eg. 60 fps for a game). Still, not everything needs to be optimized to the extreme: the algorithm to sort an in-game inventory does not need to handle 4B+ items.
- IshKebab 4y agoThere's a big difference between "premature optimisation" and thinking about performance. You shouldn't take that quote to mean you can entirely ignore performance 97% of the time.
- gnuvince 4y agoAnother quote from Knuth, from that same paper: > The improvement in speed from Example 2 to Example 2a is only about 12%, and many people would pronounce that insignificant. The conventional wisdom shared by many of today's software engineers calls for ignoring efficiency in the small; but I believe this is simply an overreaction to the abuses they see being practiced by penny-wise-and-pound-foolish programmers, who can't debug or maintain their "optimized" programs. In established engineering disciplines a 12% improvement, easily obtained, is never considered marginal; and I believe the same viewpoint should prevail in software engineering. Of course I wouldn't bother making such optimizations on a one-shot job, but when it's a question of preparing quality programs, I don't want to restrict myself to tools that deny me such efficiencies. That paper was published in 1974 and yet it captures the mindset of many a programmer in 2023 perfectly. The part I like about this paragraph is the "easily obtained" sentence; I saw a comment from someone mentioning that they made a JavaScript program 10 times faster by replacing the common functional programming combinators (map, filter, reduce, etc.) with for loops. I think most of us would say such a change is easily obtained, does not make the code impossible to debug or maintain, and gives such a massive improvement that it should be a no brainer to reach for it.
- guhcampos 4y agoI 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 levels of abstraction". We can go further: you won't be going anywhere in computer graphics by trying to "write pure functions" or "avoiding side effects". These "clean code principles" are, however, rather useful for large, corporate systems, with loads of business process rules maintained by different teams of people, working for multiple third parties with poor job retaining. You don't need to think about vector performance for processing credit card payments. You don't need to think about input latency for batch processing data warehouse jobs, but you need this types of applications to work reliably. Way more reliably than a videogame or a streaming service. Right tools for the right jobs, people need to stop trying to hammer everything into the same tools. This is not only a bad practice in software, it's a bad practice in life, the search for an ever elusive silver bullet, a panacea, a miracle. Just drop it and get real.
- gjulianm 4y agoExactly! I work with a lot of high-performance code and also a lot of non-high-performance code (think all the plumbing around the core computation) and I definitely use a lot of "clean code patterns" in the non-performance-critical parts. They're the ones that tend to change more, that more people touch, that get done faster... It's just about knowing what to use and when.
- tomn 4y ago> you won't be going anywhere in computer graphics by trying to "write pure functions" or "avoiding side effects" Not sure about this; in my experience (in a different domain, audio processing) you totally can get away with both of these a lot of the time. Function inclining works well, so you can write small pure functions in a lot of cases (especially if you accept a function that reads from one buffer and writes to another as pure). As for avoiding side effects, this is normally more about keeping your state updates small and localised (allowing more parts to be pure), which is often not a problem performance-wise. IME it's much easier to improve the performance of a piece of code which is easy to reason about and change with some level of confidence that your optimisation will not break things. I know there's some unavoidable global state in computer graphics, but presumably there is lots of code that doesn't directly touch that.
- davydm 4y agoYay, yet another contrived example to support somebody's position that a process which works fantastically in at least 90% of the time - and yes, I say 90% of the time (as a low-ball) because there _is no perfect process, framework, ideology, <insert x here>_. Everything has compromise. The compromise to make all code chase performance at the cost of maintainability is rubbish as a blanket choice, but may be necessary in certain niche situations.
- strken 4y agoI don't think the tradeoff here is between clean-but-slow code and fast-but-dirty code. It looks more like extensible-but-slow vs fast-but-locked-in. This is pretty obvious - that's why you need the indirection! It's not to satisfy some arbitrary aesthetic principle of cleanliness, it's to make the concept of a shape extensible to anything the calling code wants. Within one codebase the two behave the same because you can just go rewrite your functions, but if those functions are locked away in someone else's library the "dirty" code flat-out prevents you from ever using more shapes than the library author implemented. Want to add a rhombus? An arbitrary polygon? An ellipsoid? Something defined by Bezier curves for some reason? Well, you just can't; sorry champ. It's an interesting tradeoff to consider, though. Perhaps we write code like library authors too often, or optimise for extensibility when it isn't needed.
- Fell 4y ago> Our job is to write programs that run well on the hardware that we are given. I actually believe "the hardware that we are given" is the entire root of the problem. Most programmers work and test using whatever hardware is current at the time, but this is makes them blind to possible performance issues. Take whatever you're working on, and run it on the hardware of 5-10 years ago. If you still have a good experience, you're doing it right. If not, you should probably stop upgrading developer machines for a while. Whatever your minimum hardware requirements are should determine your development machines. This way, you will naturally ensure your low-end customers have a good experience while your high-end customers will have an even better experience. My game studio has been doing this for years. It saves money for expensive hardware, it prevents performance issues before they arise and it saves developer time for not having to overthink optimization.
- fellerts 4y agoI don't think the "job description" is accurate though. Most programmer's jobs is to write code that runs well _enough_ on the hardware that we are given. What "well enough" means depends heavily on whether you are working on firmware, a game engine or a web application. If performance isn't important, you end up with slow software.
- veidelis 4y agoI agree with Casey. I think he understands way much more than how to optimize for 60fps as many commentators here point out.
- rohith2506 4y agoDamn, people are going bananas left and right about this article. I don't think Casey is not targeting general programmer audience where sub millisecond performance does not matter as long as the user experience / business needs are satisfied but this is highly relevant in the world of HFT where you will try to optimise every instruction you execute. He never mentioned that people should write horrible code for performance but more like pointing out. Personally and professionally, We stay away from virtual functions as long as possible due to unnecessary vTable lookup every time you want to call a method
- DeathArrow 4y agoHere are some good books on the subject: https://www.manning.com/books/data-oriented-programming https://www.manning.com/books/data-oriented-programming https://www.dataorienteddesign.com/dodmain/ https://www.dataorienteddesign.com/dodmain/ Also, some good resources are listed here: https://www.dataorienteddesign.com/site.php https://www.dataorienteddesign.com/site.php
- brlebtag 4y agoOmg... I am pretty sure that jumps are more efficient than if statements... I did not see him trying this too...
- davidgrenier 4y agoThis thread is surprisingly back today with millions of comments. I don't know if anyone has pointed out that the functions called... do nothing. Hence it is understandable that the performance profile is dominated by dynamic dispatch. Also, his code must have been compiled with an old compiler or less than -O3 as the switch/table version of the code performs exactly the same with Clang and g++ when compiled with -O3. disclaimer: not a fan of OO regardless.
- beeforpork 4y agoGenerally,it is good to give advice to write clean code. Undoubtedly, clean code causes less problems than unclean code (e.g., overengineered, overmodularized, or prematurely optimised). Running speed tests on the much-cited mini-example with geometric shapes and their area is unfair and unrealistic, and it does not prove any point. I think I can see where this is coming from: 'overly clean' OO style will split concerns into virtual one-liner functions without context distributed throughout the universe. For a simple problem, I prefer 'switch'. But that's not a good rule either. For anything extensible, like a GUI, 'switch' would be the wrong choice and virtual much better. Programmers need to develop a feeling of appropriateness, and restructuring may be necesaary at times. BTW, the manual loop unrolling in the article is broken and not advisable at all. I'd be angry in code review about such 'optimisations'.
- gabssnake 4y agoThe author of the video is apparently referring to Uncle Bob’s first book (Clean code, 2009), which essentially says that “Clean” code is “understandable” code: created with care, thinking about the next reader. So yeah, the book then goes on for a painful 450 page ramble, opinions, and admittedly arbitrary rules. But Martin was at least partially aware of this: > “Clean code is not written by following a set of rules” — quote from the book! So really, the person in the video failed to apply the Principle of Charity, which is fundamental in critical thinking. They end up not addressing the interesting claim, and openly attacking a Straw Man. As for the deeper points implied in the video, they seem –ironically– less fresh: - Software is slow these days - Performance matters - The way you write code impacts performance - Don't blindly follow rules and generic advice Groundbreaking! If anything, the video shows the failures of C++ as a language. Why aren't languages designed to promote maintainability without sacrificing performance? :Rust enters the room: The more interesting claim that the video's author missed: > “It is not enough for code to work.” ― quote from the book
- bonede 4y agoyes, he's looking at you, app and web developers
- bonede 4y agoyes, he's looking at you, web and app developers
- e-dant 4y agoFor the record, runtime polymorphism is generally frowned upon in the most modern C++ practice. The only difference between “modern, clean” C++ and the author’s switch is probably a concept that requires some type attributes. The example is contrived, and the realization of “clean” code through runtime polymorphism is both dangerous and odd. The whole point of not using polymorphism is to catch runtime crashes at compile time, reduce overhead and improve readability. I know many people who wouldn’t use an object here anyway. Free functions would do nicely, and are infinitely compositional.
- sebastianconcpt 4y agoThis is promoting early optimization, precisely to the people that needs to evade doing that. Added to the pile of #HorrificAdvice and #TerribleGeneralization.
- doty 4y ago> Look, most modern software is spending 99.9% of the time waiting for user input, and 0.1% of the time actually calculating something. I'm sorry to say that this argument is not even wrong. As programmers, it is not useful to us to think about that 99.9% of time. The 0.1% of the time is literally our entire job. "Most of the universe is not Earth, so why do we spend so much time thinking about things on Earth?"
- tomxor 4y agoThe no.1 piece of advice I give to junior programmers now, or any programmer trying to improve, is to care about your code, everything else can naturally and more safely emerge from that one principle. The problem with laying down a bunch of arbitrary rules is that they never apply to all scenarios. As the person coming up with the rules you can easily re-evaluate where and when they don't work, but the novice receiving those rules wont necessarily have an intuition for the reasoning behind them yet, and so wont so considerately apply them. For everyone else, they need to understand that there is no silver bullet, no 10 commandments that will give them the best result, life is messy, and they need to think, develop their own intuitions by interrogating their own code in each new context - but it all starts with caring about your code, not being satisfied with a pile of spaghetti, or a pile of OOP just because OOP, or a pile of strictly pure functions just because FP. Every single rule or programming pattern is wrong given enough contexts, it's all subjective. Discussing patterns and rules is useful, but only if they are only used as a mental anchor to think about them, not some kind of axioms of programming correctness.
- crabbone 4y agoI patiently waited until the end of this video, hoping there'd be a punchline... but, turns out it's one of those C++ selfawarewolves kind of thing. I mean, dude discovered C++ compiler sucks after over 40 years of trying to make it not suck so much, but ignores the fact that his tools are broken and proceeds to make completely unwarranted conclusions from that. Needless to mention that software needs to be first and foremost correct. "Clean code" is about reducing the chance of a programmer of making certain kinds of mistakes. And even in the situation where the compiler sucks, it's still worth doing / paying the price in terms of speed, if you can get more confidence of your code doing what it's supposed to. Just like structured programming, "clean code" is an attempt to reduce complexity the author of the code has to deal with. ---- The proper conclusion that should've been the result of his experiments should've been: maybe something went wrong with the language and tools I'm using that even after a massive effort over several generation of programmers and mega-corporations backing that effort, the tools and the language still suck. So, the desirable properties of my programs (i.e. simplicity and ability to be extended) still come at a huge cost.
- lysecret 4y agoWell in my experience it is generally true that when you start optimising things get less clean. Let me explain: most of the optimisation situations I had looked like this. Hey this query is pretty slow and costs us quite a bit. Oh look for this type of data it’s super easy we can just return this and then the other rest of the data we can now assume this. So you have broken a single clean and nice case into two slightly less clean but faster cases. And this breaking apart then continues becoming less and less clean because you rely on some obscure characteristic of that specific type of data.
- puterich123 4y agoHe’s basically showing data oriented design, where you, try to limit cpu cache misses by operating on the data. This approach can be way faster, but is only relevant when you have a lot of entities you need to iterate over. If you have 3-100 objects it would of course still be faster but by negligible amount
- dmtroyer 4y agoGlad I don’t work with this person.
- Myrmornis 4y agoSometimes I see very good programmers writing functions that IMO are much much too long and have far too much internal state for one function. I believe there might be a correlation between these people having C++ backgrounds. Other than that I'm just mentioning it as an observation.
- buster3000 4y agoEhh. I think the author is a little blinkered here based on these examples.
- elvispt 4y agoClean code is about developer performance - understand stuff fast - not about hardware performance.
- Pesthuf 4y agoI wonder if a compiler that uses LTO could optimize these vtable calls to the same kind of code the switch statement can be optimized to.
- razzimatazz 4y agoI think the post invites a question to all the HN responders: What would it take for the computer [software] you are using to run at its full blazing potential? The answer is - if every single piece of software was written while already knowing the true requirements, the scope of its use and re-use, and knowing the future bugs and security flaws that would appear, then it could be written one time and be BLAZING FAST. Many parts would still be written in a 'Clean code' style for the necessary extensibility and testability, etc. But many others would be small and near optimal. THEN on top of that, if the author or an equivalent talent came along and rewrote or supervised the optimization of the regular software, similar to how the article does, your system would be HYPER INSANE BLAZING FAST. If we are proponents of OOP or Clean code, we need to acknowledge that fact. (i.e. My code may not be important but it all contributes to slowing down the computing world). And if we think the Author is preaching gospel here, you should also acknowledge that because the future is so often unknown when we write code we often have no choice but to fill it with Clean code that can be easily changed later, and sometimes even 'Shit code' that we thought would never be used by anyone.