3 ms·
The 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 pr
by unconed 4y ago
The 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.
- TeMPOraL 4y ago> 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. In some sense, it is a good thing. It creates natural backpressure to scope creep. The "Clean Code" developer can add a new complex shape type to their program in 15 minutes; just subclass here, implement the methods, done, no changes to other code. No performance impact - the program remains as badly performant as it was before. The Casye-style developer will take 15 minutes and come back to you with: "Oh, we can make the calculations for this new shape approximate to, idk. +/- 50%, by adding another parameter to the common equation; this will, however, cause 1.1x performance drop across the board. We could make it accurate by special-casing, at the cost of 2-3x perf drop for the whole app. We can also spend a person-week looking into relevant branch of mathematics to see if there isn't a different equation that could handle the new shape accurately with no performance penalty." "Or, you know, we could just not do it at all. Why exactly do we need to support this new shape? Is supporting it worth the performance drop?" Whichever option the developer and their team chooses, their software will still remain an order of magnitude more performant than the "Clean Code" style. Which is to say, those devs are aware of the costs - the "Clean Code" style is so ridiculously wasteful, as to not even notice such costs in the first place.
- unconed 4y agoBeing able to handle arbitrarily polygons isn't insane complexity, it's actually the minimum viable complexity needed to handle any basic shape a user might want to use. Limiting them to a handful of precomputed ones is the kind of limitation only a software developer can live with and love. And once you can handle arbitrary polygons, writing and maintaining special case code has to be justified with evidence that your code is spending enough time on area calculations that it's worth optimizing and maintaining extra lines of code for.
- mtrower 4y agoIf your area of expertise is putting out a general purpose shape area compute library, then maybe. If your purpose is elsewhere and this code is a tool to accomplish that --- not so much. Not everything needs to be fully general. Write the tool you need, whatever that ends up being.