Control over memory handling is one of the big reasons to build an engine from scratch for the game. The constant calls to new and delete slow down performance and end up messing with the experience. The main cause of performance downgrade is that the control flow. Whenever we call new or delete, the control is taken from application and kernel handles the deletion / allocation of the resources. Now task scheduling is based on the kernel’s priorities and you never know how that’d turn out for you.
So the idea behind this epic is to reduce the number of this control jumps and allocate memory more efficiently. This is achieved by the stack and pool allocators.
POOL ALLOCATOR:
As the name suggests its a pool of the large chunks of memory. When memory manger initiates, it creates a bunch of pools and separates almost 1 gigs of memory into pools of with different chunk sizes.
When request for block comes in, it finds appropriate pool and returns a block from it. This is done in O(1) time complexity.
Usage:
Pool allocators are mainly useful for the objects which are constantly created and deleted as both the operations here are done in O(1).
Drawbacks & Mitigations:
As we create a big chunk of memory at the beginning, not all the memory is being utilized. So we could manage the memory more efficiently and manipulate the allocation size. This could be fixed by profiling the memory usage and tweaking chunk size or pools count.
There are cases of cache in-coherence when you create a big chunk of array is deleted. But this case won’t affect the gameplay team is going for as we will not be changing a large number of components unless we need to load a new scene.
STACK ALLOCATOR:
In stack allocator we allocate raw memory of 1 gig and then we give out block from it as they are requested. They could be of random size.
Usage:
Stack allocator is mainly used for the data which will stay constant for the level, for example models, lighting details, textures etc.
Drawbacks:
Developer is not supposed to delete the data, once it is loaded into stack as it’d created fragmented memory. This is affordable with pools as they are very small chunks and wouldn’t cause big delays if page miss occurs. But stack deals with larger sizes we don’t want to create a fragmented chunk there.
INTERFACES:
Both the allocators provide 3 types of interfaces for using their functionality,
Assume you have a class as following,
Class Person
{ int age; string name; public: Person(); Person(int age); Person(int age, name); }
//Allocating a variable:
/Creating objects using different constructors //Default constructor Person* ptr = fmemory::fnew<Person>(); //Constructor with params Person* ptr = fmemory::fnew<Person>(10); Person* ptr = fmemory::fnew<Person>(7,"CR"); //Deleting the object fdelete<Person>(ptr);
Allocating an Array:
To create an array of objects, you will use fnew_arr and make sure that first paramter is the count of the array elements. Deletion function remains the same. Please go through following example.
//Creating an array //Default constructor. This will create an array of 10 elements with the default values. Person* ptr = fmemory::fnew_arr<Person>(10); //Constructor with params. This will create an array of 10 elements and initialize them to the parameters passed by //calling relevent constructor. Person* ptr = fmemory::fnew<Person>(10,15); Person* ptr = fmemory::fnew<Person>(10,7,"CR"); //Deleting the object fdelete<Person>(ptr);
Using in an STL container or boost container:
The allocators provide the STL interface to work with the Standard containers and boost containers which can be used as follows,
vector<Person, fmemory::STLAllocator<Person>> vPerson;
PERFORMANCE PROFILING:
It won’t be of any use if we can’t confirm that what are doing is better than standard ways unless we have numbers to back that. Following image depicts the total time taken to allocate and delete 100000 blocks with new and fnew. And we can see the improvement,
Resources & References:
CppCon 2017: Bob Steagall “How to Write a Custom Allocator” - (https://www.youtube.com/watch?v=kSWfushlvB8)
Taming dynamic memory - An introduction to custom allocators in C++
(https://www.youtube.com/watch?v=FcpmMmyNNv8)
Thanks for the memory (https://blog.feabhas.com/2019/03/thanks-for-the-memory-allocator/)
Comments