Live data from Hacker News

“Clean” code, horrible performance

computerenhance.com

811–820 of 932 posts

Re: “Clean” code, horrible performance

#811

Earlier quoted context omitted.

Moore's Law concerns IC transistor counts, not actual overall performance, and especially not single-threaded performance: a 40-core CPU isn't going to make Windows twice as fast as a 20-core CPU. Single-threaded performance has long-since effectively plateaued: it's 2023 now and a desktop computer built 10 years ago (2013) can run Windows 11 just fine (ignoring the TPM thing) - but compare that to using a computer f…

Real-world evidence says otherwise https://stoneridgetechnology.com/company/blog/the-exponentia...

That article doesn't contradict my post, in fact it's basically the same thing I'm saying: look at fig2 (the timeline graphic) and the paragraph preceding it: it shows that the gains in "serial" HPC performance gave-way to massively-parallel gains sometime around 2010.

The rest of the article is concerned with how software today is still written for those "serial" processors in-mind and fails to take advantage of parallel computing hardware - but this is hardly a new nor controversial statement.

Re: “Clean” code, horrible performance

#812
post #179

I 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 perfo…

There is speed and there's the perception of speed. Some code (games) has to run fast. But most of the code we work on has to only have the perception of speed. If you're loading all of your resources and making the user stare at a twirly, you're doing it wrong.

Re: “Clean” code, horrible performance

#813
post #179

I 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 perfo…

I would argue that ignoring performance, a lot of "clean" code isn't really that much clearer and more maintainable at all (At least by the Robert Martin definition of "Clean Code"). Things like dependency injection and runtime polymorphism can make it really hard to trace exactly what happens in what sequence in the code, and things like asynchronous callbacks can make things like call stacks a nightmare (granted, y…

> a lot of "clean" code isn't really that much clearer and more maintainable at all

Oh, it's not. Look at the crap the Java community used to produce. 37 levels of abstraction is not maintainable.

Re: “Clean” code, horrible performance

#814
post #263

Earlier quoted context omitted.

100%, I’ve done tonnes of (backend) performance optimization, profiling, etc. on higher level applications, and the perf bottlenecks have never been any of the things discussed in this article. It’s normally things like: - Slow DB queries - Lack of concurrency/parallelism - Lack of caching/memoization for some expensive thing that could be cached - Excessive serialization/deserialization (things like ORMs that create…

>> Lack of concurrency/parallelism Definitely get the single-threaded house in order before attempting to speed up by running in parallel.

Depends. For example, if the slowness comes from sequentially emitting a lot of http requests, a lot of performance can be gotten from doing it concurrently.

Re: “Clean” code, horrible performance

#815

Earlier quoted context omitted.

Real-world evidence says otherwise https://stoneridgetechnology.com/company/blog/the-exponentia...

That article doesn't contradict my post, in fact it's basically the same thing I'm saying: look at fig2 (the timeline graphic) and the paragraph preceding it: it shows that the gains in "serial" HPC performance gave-way to massively-parallel gains sometime around 2010. The rest of the article is concerned with how software today is still written for those "serial" processors in-mind and fails to take advantage of par…

You started your post saying that there's no correlation between transistors and performance. First graph shows that is wrong.

My argument was simple: 20 years ago nobody cared about performance because of the strong correlation between Moore Law and MFLOPS and performance in general.

Nowadays with multicore, individual CPU cores are becoming slower, not faster. Then we can't just wait 18 months for individual cores to get faster.

Today we need to write better software.

Re: “Clean” code, horrible performance

#816

Earlier quoted context omitted.

> A function's behaviour should have no reason to change after its behaviour is documented. That's only true with spherical cows. That something happens is a requirement. When it happens is often only as specific as 'before' or 'after' but tests often dictate that they happen 'between', which is not an actual requirement, it's an accident of implementation. It was 'easy' to put it here. Nowhere is it written that beh…

> Nowhere is it written that behavior in a system is strictly additive. For a unit of the same identity to suddenly start doing something different is plain nonsensical, never mind the technical challenges that come with breaking behaviour that should scare anyone away from trying. Logically, a unit is additive until the unit are no longer used, at which point it can be eliminated. > But the old tests disappear with…

You should reread “Refactoring”.

Refactors compose. In three months you can completely rearchitect a module without breaking it at any point in the process. That’s the promise of refactoring.

Functions don’t have an identity. There is no such thing. I don’t know who taught you that but they have broken you in the process. Renaming things is a refactoring. We don’t check the entire commit history to make sure that function name has never existed. Only that it hasn’t existed recently. There’s no identity.

One of the reasons to refactor is that the function has been lying about its responsibilities. So you extract steps out of it, create a new call path that fixes the discrepancy, migrate the call sites, delete the incorrect function, and then, if the function name was really good, you might wait a while and rename the new function to the old name. Each step makes sense if you’ve followed the entire process. If you haven’t been following along at all then you have absolutely no idea how things got here until you read the git history thoroughly, which some people can’t do, and others won’t do if they expect the code to be static.

Re: “Clean” code, horrible performance

#817
post #557

Earlier quoted context omitted.

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.

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 av…

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

#818

Earlier quoted context omitted.

The thing that sets him off is that he is using a computer with enormous computing power and everything is slow. He does have a narrow view, but it does not make his claims invalid. I liked that his POC terminal made in anger made the Windows Terminal faster. But even in that context it was clear that by making some tradeoffs - which the Windows Terminal team can not make (99.99% of users do not run into the issue, b…

> The thing that sets him off is that he is using a computer with enormous computing power and everything is slow. If that's his complaint, then "clean code" isn't the problem. The problem is capitalism and/or human nature. Once something performs acceptably well, ie good enough to sell it, performance isn't going to get any better. Flashy stuff and features get you money, going from 400ms to 100ms gets you...nothing…

> going from 400ms to 100ms gets you...nothing.

According to Amazon [0] that'd be a 3% gain in sales (assuming the inverse holds true as getting slower, anyway).

[0] https://www.gigaspaces.com/blog/amazon-found-every-100ms-of-...

Re: “Clean” code, horrible performance

#819

Earlier quoted context omitted.

> there's no guarantee that the function call you see is doing what you remember it doing a year ago. TDD provides those guarantees. If someone changes the behaviour of the function you will soon know about it. That's significant because Robert 'Clean' Martin sells clean code as a solution to some of the problems that TDD creates. If you reject TDD, clean code has no relevance to your codebase. As Casey does not seem…

You seem to ignore that when the unit changes, the tests do too. If you come back a year later, foo.bar.baz(quux) might have been refactored and lazily so. The tests were also updated and still pass. You may jump into the code only to realize that someone no-op'd everything and never removed call sites. TDD is primarily a design tool, not a lock-into-implementation tool.

Someone has put some notion of function identity in his head and I don’t know what school taught that notion but it needs to burn.

IBM’s Visual Age tried to behave that way and it didn’t end well. Eclipse dropped that conceit when it forked.

Re: “Clean” code, horrible performance

#820
post #685

Earlier quoted context omitted.

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.

I don’t recommend adopting “Clean Code” either for similar reasons.

Fair enough!
Post reply on HN