3 ms·
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
by jrumbut 4y ago
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."
- dsego 4y agoIt 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 https://caseymuratori.com/blog_0015
- whstl 4y agoI 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.
- jrumbut 4y agoI'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.
- ablob 4y agoHis 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.