Jump to content
  • Advertisement
Sign in to follow this  
hyyou

Apply memory pooling "Alloc()" in **real** game (with ECS)

This topic is 600 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have a entity-component-based game engine.  

There are many systems, e.g. System_A, System_B ,... all derived from SystemDefault.

If I want to use custom memory allocator, how can I used it in a real game?  

 

Question 1: Where is the field "allocator", how many of them?

Solution 1A :  Store it in a top header.   There might be 3 types: stack, one frame, and heap.

Disadvantage:

Many systems will tend to use the share allocator, thus this solution may not give much advantage about cache coherence.

 

Solution 1B :  Store it in each and every System.  (1-3 allocator per System)

The most appropriate way for a big game is adding a field "memory pool" for SystemDefault.

Disadvantage:

Not all memory-allocation demand is in System_xxx. 

For example :-

  • Some AI or physic engines use its own architecture that is not component-entity.    
  • Some custom datastructure may contain another small datastructure.
  • For these cases, how should allocator be passed?

Solution 1C : Store it in each and every "Allocator holder"

Make every class that want to allocate some memory derived from a certain class AllocatorHolder.

Disadvantage: crazy?

 

Question 2: How to enforce a code to use a certain allocator?

Solution 2A : Use allocator of the system that contains the code.

Disadvantage: There is a call like :-

 

//System_A  or System_B

fA() or fB(){

    loop{  

        array=System_C::doSomething();

    }

}

 

I don't think it is good - System_C::doSomething should use allocator of A, and B for each situation.  (right?)

That is C should aware what class is using it - not just blindly use its own C allocator.

 

Solution 2B : Passing allocator when calling every function.

DisadvantageThe call will be :-

 

//System_A  or System_B

fA() or fB(){

    loop{  

        array=System_C::doSomething(this);  //or this->stack_allocator or this->oneFrame_allocator, etc.

    }

         }

Now, every function practically should have allocator as a parameter - ugly.

 

Question 3: How to initialize datastructure?

In my old code it is easy like this:-

//inside SystemA
    fA(){
         MyArray<T> ts;
    }

Should I make it more harder like :-

    fA(){   
         MyArray<T> ts;
         ts.setAllocator(this->stackAllocator);    //applicable for 1A 1B 1C
    }

or ...

    fA(){
         MyArray<T,STACK_ALLOCATOR> ts;    //applicable only for 1C
    }

------------------------------------------------------

 

There are a lot of memory pool resources in the internet, but none mention how to use it in practice.

Here is what I read:-

How do you manually manage memory-allocation in your game?

Are there any good books about this?  (which show more than the pool code + trivial usage)

Edited by hyyou

Share this post


Link to post
Share on other sites
Advertisement

Thank a lot, frob.

 

It may be just my fear after read some memory-pool articles.        

 

1. In many parts of my code, I allocate temporary MyArray<T> (and similar things) as information used to communicate between a pair of system.

 

These information are mostly deleted in one frame, but I can't guarantee it.   

 

2. In some components, there is MyArray<T> as a field, so there might be some cache miss, e.g.

class ComponentA{
    MyArray<DataB> aFieldArray;   
}

I am scared that memory will be fragmented because of two causes above, so I think memory pool is the solution.     

 

I don't want to wait until the problem appear on surface, because refactoring might become very hard later.     

I don't even know how the well-written game call the allocator in practice, so I can't evaluate how hard it is to refactor my code later.

Edited by hyyou

Share this post


Link to post
Share on other sites
It sounds more like you are afraid of a problem rather than actually having the problem.

These days nearly everything runs in a 64-bit environment, with Steam's survey reporting that less than 2% of game players are still running 32-bit environments and almost no modern games support it. On a modern PC game that fragmentation problem is extremely unlikely, unless many factors are true. You would need a lot of memory relative to the address space, such as 32-bit address space and using most of it, and you have many long-lived objects mixed in with many short-lived objects. Your description has a mix of many types of short-lived objects.


You already pointed out how the last parameter to the standard library containers is an allocator object that defaults to the standard allocator. Most of the good allocator systems --- like Boost's various allocators --- can be used as that parameter. They are fairly well documented with examples, all you need to do is create an allocator and use it as the last parameter.

Boost provides many good allocators that solve various problems. Pool allocators, special alignment allocators, shared memory allocators, and more. They each attempt to resolve different problems, so make sure the solution matches your actual problem. I wouldn't bother doing it for your imagined problem, though.

Share this post


Link to post
Share on other sites

 

These days nearly everything runs in a 64-bit environment, with Steam's survey reporting that less than 2% of game players are still running 32-bit environments and almost no modern games support it. On a modern PC game that fragmentation problem is extremely unlikely, unless many factors are true.

 

Wouldnt completely agree with that, sure old and/or small games dont event hit a range of space where fragmentation could be a solveable problem but games are getting more and more memory consuming where memory is rare on most medias like consoles or mobile. Especially beginners and/or junior game developers do mistakes in memory management. I have had a game I was working on that inherited a massive 4 GB memory use on initial startup because some people did link all sound assets into it statically in Unity3D. The result was that unity just loaded all 3 GB sounds into memory when starting. So you might see that memory management is also something to master and using std::ref_ptr<T> does not realy counter a missused memory allocation management.

 

I found this blog post usefull and inspiring for me and my allocator system. The main advantage is to find memory leaks in various systems when doing tests or running in real production code and help separating memory over various systems to handle it more performant. You might for example use buckets behind your allocators to allow pooling of different memory size into different size managed pools.

 

I solved the initial allocator problem by reserving a small 64 byte piece of memory behind some global static functions to allocate my first allocator in. Then anything else is managed by my first allocator.

 

A second usecase for me is doing memory profiling. Any allocator I have reports profiler data about pointer-address and size to my profiler backend so I could see malloc/free in real time

Share this post


Link to post
Share on other sites

These days nearly everything runs in a 64-bit environment, with Steam's survey reporting that less than 2% of game players are still running 32-bit environments
Ah, if only that was true, that would be too good. That would mean we could finally just ignore the cruft and move on.

 

However, I'm afraid we're not there yet. Although it's true that there exists virtually no system that couldn't in theory run in a 64-bit environment, and the vast majority of systems (9/10) indeed does run in a 64-bit environment, those 2% are a bit too optimistic.

 

(I'll refer to December 2016 numbers)

 

If you look at the numbers reported for Windows only (looking at MacOS and Linux is kinda pointless for two obvious reasons), you will see that the 64-bit versions sum up to 87.64% and the 32-bit versions sum up to 7.98%. There's even a not-zero-percent share of people still using Vista and XP (scary, is it not!) in there.

All numbers combined sum up to 95.62%, which reflects the fact that Mac and Linux together have slightly less than 5% market share. If you normalize the numbers to account for that, you get a 8.35% share of 32 bit systems (and 91.65% for 64-bit, respectively).

 

That's what the Valve customer looks like, not necessarily what yours looks like. Now of course, there's always the argument that if someone still runs a 32-bit system on a 10 year old processor, then it's probably someone who isn't going to pay for your game anyway. But you never know.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!