Advertisement

taking at will space in memory code design

Started by July 22, 2022 02:53 PM
23 comments, last by Calin 2 years, 4 months ago

frob said:
Don't prematurely optimize. Use it if you need it. Don't try to make things faster until you've measured and you know you actually need to.

This being said, don't intentionally choose approaches that you know up front will be slower. I've seen “no premature optimization” used to justify baffling and unnecessary choices of things that are inherently slow when something that was inherently faster was every bit as ergonomic and sometimes even more so. Don't use a linked list when an array or vector is ergonomic to your problem, as it will almost always be slower. Don't use a hashmap for a container that will never be more than four elements in size and is indexed by a contiguous enum - you know up front that hashing the elements has a cost and many hashmap implementations involve heap allocations and cache misses. Don't use a resizeable vector for a container that will always be exactly four elements in size - you don't need the dynamic resizing and so paying the cost for that is not necessary. People really do this; I have called out every one of those examples in actual, production code reviews.

“You ain't gonna need it” applies to features that impact performance, not just architectural elements!

“No premature optimization” shouldn't be taken to mean “don't think about performance at all in your initial choices" and it enrages me that it is commonly misunderstood to mean that. It should not mean “use the slowest container available with the interface you want.” I'm tired of having to reject people's work in code reviews because they think std::unordered_map is the only data structure and they don't want to put any thought into how the machine will actually solve their problem at all.

Oberon_Command said:

This being said, don't intentionally choose approaches that you know up front will be slower.

Actually, you probably should intentionally choose approaches that you know up front will be slower. I do it all the time. I've even taken fast code and intentionally made it slower in order to make it easier to understand, easier to debug, or easier to adapt to changing requirements.

All code is optimized for something. 99% of the time, you should optimize for clarity and flexibility over performance.

Advertisement

a light breeze said:
Actually, you probably should intentionally choose approaches that you know up front will be slower.

No. Do not do that. Please. You're just creating work for later you or your successors and possibly even causing them to crunch to meet perf targets. Maybe you use a slow easy-to-stand-up thing during the prototyping stage, before your code gets near production, but it's far easier to change architectures or even container types before your code is submitted than after. The longer code is in a codebase the more barriers there are to changing it; more things depending on it, fewer people who understand why it is there or even what it is for, less time to dedicate to fixing up old stuff. I speak from the perspective of one who has had to maintain these “intentionally slower approaches" and eventually replace them when they did not support new performance requirements.

That's right, performance is a requirement in video games, and adaptability to changing performance requirements means not shooting yourself in the foot perf wise when it is just as easy to do better on that front as to do worse.

Do not create unnecessary work for your successors. Get it as close to right the first time as time allows.

I've even taken fast code and intentionally made it slower in order to make it easier to understand, easier to debug, or easier to adapt to changing requirements.

If I caught you intentionally making code that you actually intend to check in slower (at the level of performance difference I'm talking about) for the sake of flexibility, without the flexibility being an actual customer requirement, I would not be at all pleased. Over-optimizing for flexibility is how code gets over-engineered.

All code is optimized for something. 99% of the time, you should optimize for clarity and flexibility over performance.

But - and this is my actual point here - most of the time you can do that without intentionally making your code slower. There is no benefit to using a slow container that has the same interface and semantics as a fast one.

In any case, you shouldn't optimize for any of those things all the time - you should optimize for the requirements you are given, which in gamedev almost always include performance!

I think there's a difference between intentional pessimization or doing things which you know to be problematic, versus doing whatever quick and dirty thing gets the job done, versus agonizing over fine-tuning every CPU instruction. There's absolutely a continuum.

Context always matters. For hobby projects like we're talking here an individual is unlikely to ever stress a modern PC. Even with abysmal practices, and sloppy coding, one person as a hobby is unlikely to generate enough content and enough processor load that they'll produce a slideshow.

In a professional environment, in a real world game where we know we have soft realtime limits, there are plenty of things you can notice and say “you have to do something about that before it is a problem.” Pre-allocate and reserve your buffers, notice usage patterns and intentionally choose among them, I'd consider these a balance more around just getting the job done rather than tight optimizations.

There are cases where tight optimization really matters, where you get a team of people together and discuss how to adjust it. There are situations where you call in multiple senior developers, put the profiling results up on the big board, and start brainstorming. There are situations where we look at every byte that goes across the wire and we look at it to see what can be reduced or eliminated. We actually have a weekly performance review meeting at my current company but every week we're looking for microseconds here and microseconds there, we're not looking at massive rewrites or trying to save the world with Herculean efforts. Such-and-such showed up in a blip in the recording this week, so let's have someone look at it. But I've seen projects where that's what was needed, where everyone needed to focus on saving cycles and overhauling algorithms. In one game we had a bit of a mantra, going back to the designers “This is a 66 MHz CPU, we can't do that, let's look at things we can do instead." Sometime it happens, but it is less frequent than years past.

Calin said:
Is wasting memory when it can be avoided a bad thinking pattern?

Why didn't you just call the thread that instead of “taking at will space in memory code design”?

🙂🙂🙂🙂🙂<←The tone posse, ready for action.

fleabay said:

Calin said:
Is wasting memory when it can be avoided a bad thinking pattern?

Why didn't you just call the thread that instead of “taking at will space in memory code design”?

I don`t know, I like to complicate things for no reason.

My project`s facebook page is “DreamLand Page”

Advertisement

Oberon_Command said:

If I caught you intentionally making code that you actually intend to check in slower (at the level of performance difference I'm talking about) for the sake of flexibility, without the flexibility being an actual customer requirement, I would not be at all pleased. Over-optimizing for flexibility is how code gets over-engineered.

Usually the best way to optimize for flexibility is to keep your code simple, easy to read, easy to understand, and easy to change. Usually. Sometimes it actually makes sense to (for example) turn a constant into a variable, despite the performance cost, because that's the kind of flexibility you actually need in that particular case.

a light breeze said:

Oberon_Command said:

If I caught you intentionally making code that you actually intend to check in slower (at the level of performance difference I'm talking about) for the sake of flexibility, without the flexibility being an actual customer requirement, I would not be at all pleased. Over-optimizing for flexibility is how code gets over-engineered.

Usually the best way to optimize for flexibility is to keep your code simple, easy to read, easy to understand, and easy to change. Usually. Sometimes it actually makes sense to (for example) turn a constant into a variable, despite the performance cost, because that's the kind of flexibility you actually need in that particular case.

Sure, sometimes I'll do that, too - make a global constant into a variable so I can control it from the scripting system. But one can do that in such a way that it's only a variable in debug/test builds and still constant in release builds with minimal effort expended on our part; wasting debug perf is fine, that's what debug builds are for. Anyway, that's (usually) beneath the level of perf difference I had in mind. I'm thinking of algorithm and data structure and architectural choices that are wasteful without actually being sufficiently simpler than alternatives, not microoptimizations/pessimizations.

Oberon_Command said:
Sure, sometimes I'll do that, too - make a global constant into a variable so I can control it from the scripting system. But one can do that in such a way that it's only a variable in debug/test builds and still constant in release builds with minimal effort expended on our part; wasting debug perf is fine, that's what debug builds are for.

I'm gonna disagree with that slighty - wasting time in debug is fine to a certain degree. Obviously, your debug-build still needs to be able to run at an interactive frame-rate, and don't take hours to load. One main reason why I switched my scripting-backend to bytecode and later JIT was that the debug-editor was barely able to run the game at the required frame-rate, at 1x speed - and I'm using turbo-mode to speed up testing (which via fixed-timestep doesn't increase the dt but executes the update more often). The release-build of the game was way fast enough, but I'm mostly using the editor in debug-mode, since compile-times are faster and errors are easier to trace.
So under those conditions, the performance-overhead strictly in debug was way too much (TBH I needed the script-rework anyway to support more complex games, but it wouldn't have mattered if I didn't need the debug-speed).

Oberon_Command said:
Anyway, that's (usually) beneath the level of perf difference I had in mind. I'm thinking of algorithm and data structure and architectural choices that are wasteful without actually being sufficiently simpler than alternatives, not microoptimizations/pessimizations.

Agreed. Converting constants to variables is not really a performance-tradeoff. If you require the ability to configure things, then thats a feature, and you pay for the features that you use. On the other hand, using a list is never really much simpler than using a vector (minus the iterator-invalidation-constraints), so that would be a bad choice.
Though one contrary example that I like to bring from my own work is my 2d collision-detection, which currently is a simply O(n^2)-loop. Since I never have more than 50-100 collidable elements, and introducing any more optimized data-structure would require a good chunk of additional (programming) work. Thats a good example of where using a suboptimal routine is the better choice for productivity and simplicity.

Juliean said:
run at an interactive frame-rate, and don't take hours to load

Why do you say a program must run at an interactive frame rate and Doesn`t take hours to load?

the game assets are still the same regardless if its a debug or release version. Why would a debug version take more time (a lot more) to load than a release version?

My project`s facebook page is “DreamLand Page”

This topic is closed to new replies.

Advertisement