Live data from Hacker News

“Clean” code, horrible performance

computerenhance.com

511–520 of 932 posts

Re: “Clean” code, horrible performance

#511
post #208

Earlier quoted context omitted.

But 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.

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

> Fine, but be honest with yourself and admit that you are contributing a lot to making the lives of those two admins miserable

The funning thing is I'm thinking of a specific case and I work closely with those admins. I even have filled in for them when they're sick. Yes I know it's a pain, they know it's a pain, and they let me know it's horrible. The only reason this one is monthly is that it's a stock take. They forgive the crappy performance as it still saves hours of work when compared to the previous manual option of entering things into multiple systems.

Re: “Clean” code, horrible performance

#512

Earlier quoted context omitted.

I once optimised a SPA app that had to be really fast for usability reasons (industrial use), I replaced all the 'high level' JS patterns such as map, filter, and frontend framework things to use just if else and for loops and native dom manipulation, and it ended up more than 10x faster, each click would update the app in one frame, it was very noticeable. So yes CPU cycles do matter for websites, even with modern h…

I'd be interested in a blog post on this. Why is JS map so much slower than a for loop?

Approximately, the slowest thing you can do in a program is memory allocation (and garbage collection is even slower). JS map allocates an entire new array.

Re: “Clean” code, horrible performance

#513

Earlier quoted context omitted.

This isn't necessarily bad if all the other operations are also virtual. The idea is that you can quickly get something working (e.g. when porting) just by implementing setPixel(), and then gradually fill in other primitives with properly hardware-optimized versions. And you do need a virtual setPixel() in any case because some API client might need that one call.

I'm sorry, why do you need a virtual setPixel? You can still easily get something working just by implementing setPixel without virtual dispatch, the linker has no problem inlining that call at compile time. If some arbitrary API needs it to be virtual it's easy to implement the virtual call in just that specific case, instead of burdening your entire system with a virtual call that'll always be static in practice.

For the same reason why you need a virtual drawLine etc. Because some code wants to render something, and doesn't want to care if it's rendering onto an on-screen surface, into a file etc.

Your entire system will not be burdened with virtual calls in places where you use concrete implementations (so long as they're final/sealed, anyway). The overhead is only there if you try to use the abstraction generically, but why would you do that in the case where the virtual call "will always be static in practice"?

Re: “Clean” code, horrible performance

#514
post #507

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

> So 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"? You're mistaken, the load size has nothing to do with the end result. The result is normalized to give an estimate of how much faster the simple code is than the polymorphic code irregardless of input size . (Kinda like deaths per 100k instead of giv…

> Especially when you make every class an interface, with... get this, one implementation! This is based on real world experience and is not a joke.

And when you run this through a profiler, you will not notice how slow your code is, because everything is slow. Slowness is infused throughout the whole system.

Re: “Clean” code, horrible performance

#515

Earlier quoted context omitted.

It should be noted that there were AAA games written in this fashion, and they were not slow. All method dispatch was virtual in UnrealScript, for example.

Well, for starters, many AAA games in Unreal had to have many core functions/classes rewritten from UnrealScript to C++ for performance reasons, where often not every call is virtual. Secondly, UnrealScript is not really a great example, since on-top of Unreal being notoriously on the slower-end of game architectures, and even Epic decided to drop UnrealScript. And importantly, UnrealScript was designed in the 90's,…

Of course C++ is faster, although that has more to do with being compiled rather than bytecode-interpreted. But even so, we played those games on hardware that's very slow by modern standards, and it was fast enough for competitive PvP, so I wouldn't describe it as "slow" in absolute terms.

Re: “Clean” code, horrible performance

#516
For 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.

Re: “Clean” code, horrible performance

#517
post #263
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…

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…

I'd actually say this article is generally unhelpful - it's good to be aware but as someone who works on sorting out performance critical things I want the code to be as clean as humanly possible going in. Whether you write clean or dirty code if you're a junior developer you're probably not going to write performant code and even senior devs may be able to sniff what might be a bottleneck in advance but most of us have learned to avoid premature optimization like the plague.

Maintainability and cleanliness is the best virtue code can have. If you have extremely clearly written code that has performance issues I can swoop in with analysis tools figure out where the pain point is and refactor it out. Sometimes this is a real headache[1] sometimes not - what I can guarantee is that if the code is "dirty" it's going to be a headache and it'll take more time.

I'd personally take issue with this article over the polymorphism claim though - polymorphism is a tool but it isn't the be-all and end-all tool. A lot of your data can live as structs/blobs in memory with tight internal type definition but without any OO principals. Personally I am a huge fan of functional programming (but not pure functional programming) so objects that I use are relatively few and far between and exist to fulfill a very specific purpose.

I've had two occasions in working when I needed to break out an asm block - the compiler was being a thick headed dummy and this code needed to receive incoming signals without exception or delay - but once that critical section was passed? Back to high level programming and statements favoring expressiveness over raw bare metal performance.

If you want an interesting experience talk to your closest non-technical manager type - be that a product team manager or the company owner - and ask them if they'd prefer if you focus on reducing how long your product takes to execute by 20% over the next five years or if they'd prefer you to lower the growth of the developer labor budget by 20% for the next five years by focusing on maintainability over performance. With the exception of extremely niche cases maintainability is always the golden standard.

1. For instance, I've dealt with OOM issues that have required transforming all logic on a query result to be lazily evaluated on a data stream after main execution finishes - like the logic goes up and down the stack and only then begins processing results. In this particular case the problem was rather easy to deal with because we essentially swapped out the actual value passing on each layer for a lazy result set being passed around - because the code was clean. Sometimes you'll definitely need to massively re-engineer things though.

Re: “Clean” code, horrible performance

#518
In 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.

Re: “Clean” code, horrible performance

#519
post #263
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…

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.

Re: “Clean” code, horrible performance

#520

Earlier quoted context omitted.

The moon doesn't fall into the ocean until it does.

Not a fair comparison :P. The point is that the developers may think O(n^2) is fine because their toy use cases had n=10...100, but then actual users will try to use the software for n=10k, or n=100k, and then either waste their lives working with suddenly slow software, or look for alternatives. I walked into a case like this the other day. I wanted to do a little semi-collaborative project planning. I found a nice…

It's more than that. The way black box composition is done in modern software, your n=100 code (say, a component) gets reused into a another thing somewhere above, and now you're being iterated through m=100 times. Oops, now n=10k

Generally, Casey seems to preach holistic thinking, finding the right mental model and just write the most straightforward code (which is harder than it looks; people get distracted in the gigantic state space of solutions all the time). However this requires 1. a small team of 2. good engineers. Folks argue that this isn't always feasible, which is true, but the point of these presentations is to spread the coding patterns & knowledge to train the next gen of engineers to be more aware of these issues and work toward said smaller team & better engineers direction, knowing that we might never reach it. Most modern patterns (and org structures) don't incentivize these 2 qualities.

Post reply on HN