do most games do a lot of dynamic memory allocation?

Started by
99 comments, last by Norman Barrows 9 years, 4 months ago

quite true, one extra de-reference per access, i believe. hardly anything to write home about, unless you do a couple million or billion per frame unnecessarily.


I'm intrigued as to where you have pulled this 'extra dereference' from?
A pointer to a chunk of dynamic memory and a pointer to a chunk of memory which came in with the exe are going to be the same...
Advertisement

my same exact question. from the video that inspired the post, it seems as though newing and deleting things like entities, dropped objects, and projectiles was something that games in general were commonly doing on the fly as needed. which struck me as inefficient and error prone.


Except in most cases the new/delete is going via a pre-allocated heap so the cost of creating and deleting isn't that great and depends on the nature of the thing being created/destroyed and most of the overhead is going to be in object deinit where it's child objects are being cleaned up, which unless you are leaving your objects in some kind of zombie state you should be doing anyway and the cost is practically identical.

As mentioned it depends on the things being allocated and how of course; big game entities are infrequently allocated and de-allocated so a sensible clean up via a new/delete (placement, in the C++ case) isn't going to cost you much in overall run time. Transient objects, such a structures which only exist for a frame, are likely to be simple and will more than likely use a simpler allocation scheme ('stack allocation' in the sense that you have a scratch buffer objects a placement-new'd into for ease of construction but never deleted from, the allocation pointer is just reset to the start each frame; useful for things like rendering when you need to build temp data structures).

games taking over memory management when asset size > ram makes much more sense. its what any good software engineer would do.


This has nothing to do with 'asset size > ram'; this is all about keeping things clean. If I don't need that memory allocated then why hang on to it? Why effectively waste it? If you know in your front end you'll need 4Meg for something but only in the front end then you might as well share that pool with the game and overlap the memory usage allowing the system to (OS) to make better use of the physical ram for other things. If you had a 4Meg chunk in the data segment then that 4Meg is now gone forever and while it might not seem much the OS can still make use of it.

If you are allocating a scratch buffer every frame of the same size then just allocate once, at start up, and just reuse it.


i'm saying - that's what i would do.


So why does the allocation speed matter? Most allocations are so infrequent as to not make a difference and frequent ones would be spotted and replaced pretty early on (or not done at all if the developers have any amount of experience).

well, fortunately for me, its not that hard to determine required data structure sizes at compile time. caveman has taken about 2-3 man-years to make. MAXPLAYERS, MAXTARGETS, and MAXPROJECILES have never changed since first being defined. occasionally i'll up MAXTEXTURES from 300 to 400 etc, as the number of assets grows. but that's about it. and i could have simply declared all those really big, then just right-sized them once before release.


And that's a very small subset of buffers which might exist in a game and a very specific example where you apparently don't care about wastage or good software design (hint: global things are bad software design but having seen your code in the past this doesn't surprise me in the least...) but as a general case solution is does not work and it does not scale. Fixed compile time buffers are the devil; the flexibility gained from just pulling from the heap for a value pulled from a config file far outweighs anything else in the general case.

The pains of heap memory allocation are usually associated with malloc, not with the heap directly. As you said, it's the allocations that are slow, not the references. This is why most game engines only allocate sparingly, in large chunks, then partition those chunks with some custom allocator. Any engine littered with new or malloc is likely not a very performant engine.

A custom stack allocated on the heap is little different than using the stack directly, or a custom stack on the data segment. You're making like, an extra pointer dereference per allocation/deallocation. That's nothing.

EDIT: On further thought, it's not even that. Incrementing a heap-stack pointer is functionally equivalent to incrementing the normal stack pointer. No extra dereference needed.

Not using the heap as a defensive strategy against bad coding doesn't seem like valid thinking to me.

Using containers and smart pointers, thinking about exception safety and avoiding raw pointers to represent ownership achieves much the same thing.

I'm glad whatever you do is working out for you and Caveman, looks like a cool game. But I don't think your approach has anything to recommend it more generally.

sometimes i think we need a term similar to "premature-optimization" to address "premature-over-engineering" - adding capabilities you don't KNOW you'll need, or adding capabilities before you know you'll need them. to me it almost smacks of "throwing code" at problems that may or may not exist. since the requirement is only anticipated, and not actual, the actual requirement details must be anticipated, and are not necessarily fully known already. so one is sort of guessing at a solution ahead of time (a form of throwing code at the problem).

We do have that -- it's called the YAGNI principle, or You Ain't Gonna Need It. See also, "Write Games, not Engines". YAGNI holds that it's better to not implement something until you have an actual need for it because you likely don't understand the requirements and ramifications of that which you don't yet have a pressing need for. Doing so is like trying to design a bridge for a site you haven't seen and have incomplete information on -- you either end up with a design that falls short of requirements, or is very general and likely over-engineered. In either case, effort is wasted and whatever work can be salvaged is not the elegant fit that it should be.

That said, I don't believe the principle applies to your allocation situation in my opinion, and you should also keep in mind that premature-pessimization and premature-under-engineering are also a thing, and can also result in wasted effort and lost opportunity.

I like the analogy of writing code as taking a journey -- You must deal with what's right in front of you most immediately, but you also know that you're headed somewhere over the horizon. You know the most about what's nearest, but you know something of the challenges you will face stretching out to the horizon, even if all you can make out is the general shape and lay of the land -- but you know nothing of what's over the horizon. YAGNI says "It's best not to bet at what's over the horizon", but it doesn't say "Ignore everything that's not immediately in front of you." either, because that would have you building solutions that will likely fall short of requirements that could be reasonably foreseen.

All of that said, I'm not attempting to convince you to change what you have done -- if it works, and continues to work for you, then who is anyone to say? But I am (and I think the rest of us here are) trying to shoot down your misconception that static allocations are beneficial in any way -- they are exactly (or at least approaching exactly-enough to not matter) the same as a single dynamic allocation with same lifetime, but also potentially less flexible. They are no more efficient and only differently complex, not less so.

Games do avoid dynamic allocations, but the why and the how are important. The why is because frequent allocation/deallocation cost cycles, the culprit is not dynamic allocation itself, but simply the quanitity of allocations. The why leads directly to How the industry resolves the problem -- fewer allocations, not static allocations.

throw table_exception("(? ???)? ? ???");

Not using the heap as a defensive strategy against bad coding doesn't seem like valid thinking to me.

That not being the main reason , though pretty reasonable reason. If you do not free memory to the very OS, you can implement :

1- pooling

2- cache rapid friendship

3- reordering in memory layout

4- reindexing

C# hiddenly implement those, that is how much those mentions are benefitial at runtime


Well I'm glad there's at least one coder out there who doesn't make mistakes. You must rake in a fortune to not have any bugs at all!

oh, i still make careless errors - its human nature. but they usually are of the "typo that still compiles" form (stegomastodon_atk_wav=119, not 120), or the result of an API i didn't document thoroughly enough, then use it a year later and forget exactly how it works. but when the code base is pretty stable, and built from components used in many games over many years, you're just doing the same sorts of things over and over with the same sorts of components, so bugs are rare, and usually due to careless errors brought about by haste, due to its all being "old hat".

the last memory related error i had was poor API documentation. Increasing chunk cache size requires additional ground meshes for the additional chunks. i increased chunk size, but didn't assign additional mesh space in the meshes database. as a result, it used the assigned mesh memory for as many chunks as it could, and didn't draw ground meshes for the rest - pretty easy to catch in testing. granted, i could write "safer code" that checked for this condition, but this is a condition that should not occur, so once the code is working right, the check is a waste.

while fixing that, i found a related "typo that compiles" bug. max_qauds_per_ground_mesh was defined as chunksize, not (chunksize/quadsize)*(chunksize/quadsize). this would result in vb's and IB's that were too small. however, this memory is allocated sequentially, so directx had no problems until the mesh memory was used up, and then it simply wouldn't draw ground meshes. this one i hadn't caught yet in testing, as apparently,the cache had never been stressed enough to run out of mesh memory, even with undersized mesh allocations. as testing progressed, i got to the point where i would stress the cache to 100% miss level.

but for me, memory related errors are pretty rare. pretty much everything is in an array[MAXSIZE] and gets accessed via for i=0 i<MAXSIZE i++, so no range check errors. variable size assets are loaded all at once onto the heap sequentially at programs startup, and released at program shutdown. the only other use of the heap i make is a temporary allocation of a 1meg buffer to read in a savegame file for a crc check. the buffer is released at the end of the crc_check function.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Not using the heap as a defensive strategy against bad coding doesn't seem like valid thinking to me.


That not being the main reason , though pretty reasonable reason. If you do not free memory to the very OS, you can implement :
1- pooling
2- cache rapid friendship
3- reordering in memory layout
4- reindexing

C# hiddenly implement those, that is how much those mentions are benefitial at runtime

Could you explain why you think you can do those things with static but not dynamic memory? Seems unrelated to me.

How one uses memory is a different issue.

Not using the heap as a defensive strategy against bad coding doesn't seem like valid thinking to me.


That not being the main reason , though pretty reasonable reason. If you do not free memory to the very OS, you can implement :
1- pooling
2- cache rapid friendship
3- reordering in memory layout
4- reindexing

C# hiddenly implement those, that is how much those mentions are benefitial at runtime

Could you explain why you think you can do those things with static but not dynamic memory? Seems unrelated to me.

How one uses memory is a different issue.

Using stack memory as much as possible is for sure desired. I mentioned the upper as the dynamic memory usage, when it is not safe to create an instance on the stack anymore.

One should then declare and instantiate up front as much as possible, and have runtime created objects to come really rare. Not only becouse of the error security, but becouse of many other benefits.

Okay.

Many other benefits such as?

Nobody is suggesting allocating memory every frame. You can pool with dynamic memory, implement movable systems etc. The difference is that limits are not hard coded into the program.

Much bad advice in this thread based on confusion of the issues.

This topic is closed to new replies.

Advertisement