News for hackers. Stuff that matters.
Re:“Clean” Code, Horrible Performance by jiggawatts on Tuesday February 28, 2023 @05:35PM (#34975906)
Casey makes the point that you don't have to hand-tune assembly code, but instead just write the simpler code. It's easier to write, easier to read, and runs faster too!

If there's something wrong with that advice, I can't imagine what it is...

Re:“Clean” Code, Horrible Performance by Aeolun on Tuesday February 28, 2023 @06:00PM (#34976142)
> If there's something wrong with that advice, I can't imagine what it is...

It will start getting really annoying when you try to add shape ‘hexagon’ and need to figure out all the places where a shape can potentially be used, just so you can update the switch statements.

Re:“Clean” Code, Horrible Performance by yakubin on Tuesday February 28, 2023 @06:34PM (#34976486)
Even in C compiler will emit warnings for unhandled cases in switch statements as long as you don’t provide a default case (as you shouldn’t).
Re:“Clean” Code, Horrible Performance by e28eta on Tuesday February 28, 2023 @07:36PM (#34977124)
Depends on the type of code you’re writing. If your `switch` is tightly coupled to the code that defines the cases and they’ll definitely be changed in lockstep, a default is more likely to be harmful.

If your cases are defined externally, and you need to be forwards compatible, omitting a default is wrong.

The Swift language specifically added `@unknown default` for switching over enums.

Re:“Clean” Code, Horrible Performance by Aeolun on Tuesday February 28, 2023 @07:48PM (#34977225)
Depends on your language I suppose. I haven’t worked with a ton of compiled languages.

But we can just re-up the problem by adding 100 different shapes instead of the one. Now you have switch statements with 104 cases each spread through your codebase.

Re:“Clean” Code, Horrible Performance by yakubin on Tuesday February 28, 2023 @08:17PM (#34977478)
I prefer to have those 104 cases all in one place (as is the case, when it is a switch statement) rather than each in a separate file, that I need to jump around between now (as is the case with polymorphism). This situation is a bit analogous to organising things column-wise vs row-wise. And in practice I find that I need to jump around a lot less with code that uses switches than with code that uses polymorphism. Tangentially, the latter is also more prone to turning into spaghetti, as the whole is obscured by indirection levels between the parts, but you don't see the spaghetti until you try to step through the code, when debugging an issue or just trying to familiarise yourself with a new codebase.
Re:“Clean” Code, Horrible Performance by andyferris on Tuesday February 28, 2023 @06:24PM (#34976402)
Many languages provide unions or sum types along with exhaustiveness checking to make this very easy (frequently not OO-inheretence based languages though).
Re:“Clean” Code, Horrible Performance by Ygg2 on Tuesday February 28, 2023 @11:46PM (#34978876)
What happens if library user wants to extend functionality? They can't inject their code into the library.
Re:“Clean” Code, Horrible Performance by piaste on Wednesday March 01, 2023 @03:31AM (#34980045)
Yeah, it's very awkward. Your best option is to leave a 'hole' case where someone may provide a 'data type' set of functions satisfying an interface, and the library author simply calls them. Effectively you're adding an OO escape clause, but it's ugly and will break user code when you add more functions and grow the interface.

Conversely, in a codebase organized by objects it's not clean to add an extra method to the base class and each subclass. You have to write an external function and switch over every known subclass inside it, which is also very ugly and will also break when you add more subclasses.

The two designs are actually the duals of each other. Someone compared it to rows vs columns and it's a great comparison.

In OO, the methods are columns and each new row is a new subclass implementing them.

In FP, the types are the columns and each new row is a function that switches over the possible types.

Re:“Clean” Code, Horrible Performance by sarchertech on Wednesday March 01, 2023 @04:17PM (#34988474)
Bob Nystrom has a good article on this (the expression problems it’s called)

https://journal.stuffwithstuff.com/2010/10/01/solving-the-ex...

He also discusses it in his book Crafting Interpreters.

Re:“Clean” Code, Horrible Performance by Olreich on Wednesday March 01, 2023 @03:34AM (#34980066)
If this is going to be a library where that’s a desirable feature, architect it for that feature. In the example, one easy way would have the coefficient table be expandable/replaceable. If you really need to run arbitrary user code, then write an interface that the user will conform to and call their code. You don’t even need OOP support to do that easily, just typed function pointers.
Re:“Clean” Code, Horrible Performance by crabbone on Wednesday March 01, 2023 @11:03AM (#34983810)
Why would you go with "many languages" into a thread showcasing how C++ sucks? I'm pretty sure that had the author of the video done all the same manipulations in Python, the speed difference would've been negligible.

The author of the video discovered that C++ compiler is dumb when it comes to optimizing virtual method calls (that instead of bare virtual method calls he had to help the compiler to guess the right conditions where these virtual calls could be replaced with guessed static calls). Essentially, all that his video is saying is: "virtual calls bad if-else good". Which is like what every C++ game-dev thinks after few years on the job. Which is amusing in how short-sighted it is, and sometimes even more amusing to discover the "solutions" created by such C++ game-devs that are aimed at replacing C++ objects, but do it in a way that's even worse than C++ original design (who would've thought that to be possible!?)

Re:“Clean” Code, Horrible Performance by jbverschoor on Tuesday February 28, 2023 @06:57PM (#34976703)
switch isn't 'invented' for enums. switch is a low-level construct which mimics several goto's / jumps. It's just as bad, except for the case where you have either: a) multiple behaviors for the same value, b) need to pass through (not break). b) is the nr 1 reason I hate switches, and my nr 2 reason is that most languages don't support proper enums, and will fail when you don't handle all possible values
Re:“Clean” Code, Horrible Performance by EVa5I7bHFq9mnYK on Tuesday February 28, 2023 @09:01PM (#34977797)
Switch was invented because it allows to replace several ifs and gotos with a precomputed static jump table, an optimization trick.
Re:“Clean” Code, Horrible Performance by radiator on Wednesday March 01, 2023 @12:35AM (#34979128)
Switch is not bad, far less is there any reason to hate it.
Re:“Clean” Code, Horrible Performance by smnplk on Wednesday March 01, 2023 @03:29AM (#34980031)
Agree. It's the same as saying don't use for loops or any other basic language constructs. Switch is very useful, please leave switch alone! You will not take switch away from me :)
Re:“Clean” Code, Horrible Performance by jbverschoor on Wednesday March 01, 2023 @05:48AM (#34980889)
When do you actually pass through? Only some state machines do that. But even I. That case, ifs are more clear and less error prone due to missing breaks, braces/scoping issues, etc
Re:“Clean” Code, Horrible Performance by camgunz on Wednesday March 01, 2023 @01:04PM (#34985912)
Conversely it's really annoying to add a new method to each shape--you've got to open them all up and add shape-specific code for them with a bunch of boilerplate. With switch statements you just add one more function.

This is the "expression problem": https://en.m.wikipedia.org/wiki/Expression_problem

Re:“Clean” Code, Horrible Performance by kyralis on Tuesday February 28, 2023 @06:01PM (#34976158)
"Easier to write, easier to read" is the part that's wrong with that advice.

It absolutely is, on toy problems like the one described in the article.

It very frequently is not when embedded in much larger domains as part of large projects maintained over years by teams.

Re:“Clean” Code, Horrible Performance by jrumbut on Tuesday February 28, 2023 @07:05PM (#34976794)
And when you combine inadequate abstractions with programmers who aren't the kind of geniuses brought in to optimize game engines you get very difficult to fix performance problems.

One of the nice things about some of the clean code concepts he uses is that (as he shows) you can tactically step back from them in key, performance critical areas and reap these wins.

If you stay too low level you get lots of tangled spaghetti code with major performance problems and no obvious way forward besides "make it better."

Re:“Clean” Code, Horrible Performance by whstl on Tuesday February 28, 2023 @10:11PM (#34978308)
I find it a bit disingenuous to call what Casey Muratori is doing "staying in the low level".

Using procedures/functions is not exactly "low level". Using switch is not low level. Lookup tables are something you have to do in high level code all the time.

Sure he could have used much better variable naming (CTable?) and probably documentation, but code-wise there's nothing that screams low level there.

Re:“Clean” Code, Horrible Performance by jrumbut on Wednesday March 01, 2023 @12:54AM (#34979223)
I'm not sure how it's disingenuous, I sincerely believe what I said and I'm not trying to fool anyone.

I would consider most of his replacements lower level than typical the clean code practices he critiques (especially the ones like iterators that he mentions but avoids in order to steel man the clean code side a little bit), not the lowest level possible. They take into account how the machine actually works and avoid additional indirection which is why they perform better.

Re:“Clean” Code, Horrible Performance by ablob on Wednesday March 01, 2023 @10:59AM (#34983743)
His code does translate relatively straightforward into haskell. Do you think haskell is a low-level language, too?

Take Listing 27, getAreaUnion for example:

  f32 const CTable[Shape_Count] = {1.0f, 1.0f, 0.5f, Pi32};
  f32 GetAreaUnion(shape_union Shape)
  {
      f32 Result = CTable[Shape.Type]*Shape.Width*Shape.Height;
      return Result;
  }
Is represented quite straightforwardly:

  {-# LANGUAGE OverloadedRecordDot #-}
  
  data Shape
    = Square    { width :: Float, height :: Float }
    | Rectangle { width :: Float, height :: Float }
    | Triangle  { width :: Float, height :: Float }
    | Circle    { width :: Float, height :: Float }
    
  cTable :: Shape -> Float
  cTable shape = case shape of -- The "lookup table" or "array"
    Square    {} -> 1
    Rectangle {} -> 1
    Triangle  {} -> 0.5
    Circle    {} -> pi
  
  getAreaUnion :: Shape -> Float
  getAreaUnion shape = cTable(shape) * shape.width * shape.height
Although it is typically easier to abstract in a "high level" language, abstraction does not require it. This whole debate is rooted on false assumptions and the need to take a side, imo. Casey has a point, it is just ignored in a typical hand-wavery fashion. "The toy example doesn't scale" is a poor argument, especially when what we can observe is slow software.

The stuff proposed in the post is not rocket science, it is a very straightforward implementation of tagged unions. Instead of fetching a vtable and jumping to a value there, he proposes to branch on the tag. This is essentially dynamic dispatch on a known set of types.

Additionally, he shows that this can result in speedups greater than a factor of 1. Any program that wants low latency or high throughput can profit from this observation.

This way of programming is by no means the one to rule them all. It has different advantages and drawbacks; none of which have anything to do with the percieved intelligence of the programmer or later consumers, for that matter.

An objective disadvantage of this style is, that the program can't interface with code, that hasn't been written yet, as a caller. Another disadvantage is that the size of the tagged union is defined by its largest "subclass".

In the end, what he has shown is that speed is often a compromise made unnecessarily. This doesn't really have to do with clean code anymore, as I can see how a compiler could implement what he is angry about with virtual functions in every situation where his style is applicable.

Casey has had a similar thing about the windows-terminal and somewhere in his videos a different, yet arguably worse, problem comes to mind: a lot of libraries do not care about performance enough. If you write a program and care, you may run into the problem that the library you use is your bottleneck. If this library is hard to replace (imagine needing a rocket-scientist), then you are done for. In that specific case it was DirectWrite and some other Windows-API that were slow. So if, for one reason or another, the windows team was required to use both, they'd have a hard limit on how fast they could go, just due to that. There is no "being smart" or "requiring a genious" involved in the forced/strongly recommended library here.

Re:“Clean” Code, Horrible Performance by dsego on Wednesday March 01, 2023 @08:39AM (#34982240)
It could in fact sometimes be easier to refactor low level code, than to dig yourself out of bad leaky abstractions.

https://caseymuratori.com/blog_0015

Re:“Clean” Code, Horrible Performance by whstl on Tuesday February 28, 2023 @10:07PM (#34978287)
On the other hand, "Clean Code", at least the variant from the book, is very frequently also extremely difficult to write or to read in larger codebases too.

The claim that more complicated "Clean Code" scales better or allows for more maintainable software hasn't been proven by anyone, and everyone with enough experience has worked with several counter examples.

The problem of code maintainability is not solved by this coding philosophy.

Re:“Clean” Code, Horrible Performance by singron on Wednesday March 01, 2023 @12:26AM (#34979084)
Sometimes people forget that "Clean Code" is a book, and it's not exactly stellar. This is a pretty thorough tear-down: https://qntm.org/clean

I'm totally onboard with prioritizing readability over performance for most code, but the style in the book has a lot more tradeoffs than it discloses, and you often don't really appreciate that until you are trying to debug if/how A transitively calls Z in a 10 million line codebase.

It's really hard to have a constructive conversation about this though since it's so subjective, any example is too trivial, and any real system is too large.

Re:“Clean” Code, Horrible Performance by bcrosby95 on Wednesday March 01, 2023 @12:39AM (#34979153)
I agree. But I also think everyone should read it at some point. Many programmers err so far on the other side of "Clean Code" that they need to be lopped upside the head with the book and maybe they'll end up somewhere more reasonable.
Re:“Clean” Code, Horrible Performance by DeathArrow on Wednesday March 01, 2023 @02:39AM (#34979757)
Do you have any example of an open source project written in the simple style suggested by Casey Muratory that is very difficult to be maintained and you think would benefit from using "clean code", "SOLID", design patterns and abstraction on top of abstractions?

If you can't point to an actual example, I don't think you have a solid case.

Re:“Clean” Code, Horrible Performance by LorenPechtel on Wednesday March 01, 2023 @05:04PM (#34988998)
This. Clean code isn't about writing stuff like this. For what he's talking about the overhead of polymorphism is a major part of the total cost and his case is simple enough that there's little value.

However, the bigger your task gets the more value there is to polymorphism and in general the smaller the percent of total time goes to the polymorphism overhead.

And note that his attack is only on polymorphism, not the other aspects of clean code. I strongly suspect the compiler optimizes away much of the clean stuff I do but I have never checked. I also find profiling easier on cleaner code, it makes it very obvious where the time sink must be and thus what warrants expending effort to improve. Profiling almost always shows the vast majority of time going into the unavoidable (say, disk reads) and a small number of other routines. Spend your optimization effort on the spots that need it because 99+% of your code doesn't run often enough for it to matter.

Re:“Clean” Code, Horrible Performance by saagarjha on Tuesday February 28, 2023 @06:16PM (#34976322)
The problem is that Casey has a very particular definition of simple which is problematic to apply in many cases.
Re:“Clean” Code, Horrible Performance by whstl on Wednesday March 01, 2023 @04:16AM (#34980316)
Does he? If anything, it is the definition of "Clean Code" that is somewhat special compared to previous usage of OOP and other paradigms.

Casey's definition of simple actually reminds me of the cliché by Rich Hickley. It's simple, but it's not necessarily easy.

Re:“Clean” Code, Horrible Performance by saagarjha on Wednesday March 01, 2023 @10:01AM (#34983116)
I don’t recommend adopting “Clean Code” either for similar reasons.
Re:“Clean” Code, Horrible Performance by whstl on Wednesday March 01, 2023 @11:03AM (#34983808)
Fair enough!
Re:“Clean” Code, Horrible Performance by moremetadata on Wednesday March 01, 2023 @01:59AM (#34979532)
> It's easier to write, easier to read, and runs faster too!

ITs still possible to get a bottleneck in assembler.

Whatever language is used, executable code still needs to be profiled using tools as described here.

https://en.wikipedia.org/wiki/Profiling_(computer_programmin...