Hello, and welcome to C++ Weekly. I'm your host Jason Turner, available for code reviews,
contracting and on-site training. This episode is going to be one that is 100% directly related
to the game stuff that I'm doing, and I need to get this one out of the way so that in a
future game episode I can apply these things. So we're gonna talk a little bit about allocators,
and I will flat out say I am NOT an expert in how allocators work with C++, but I did
watch this excellent video which I currently have up on the screen; and I will attempt to
remember to link to in the notes called "Getting Allocators Out of Our Way" from Pablo Halpern and
Alisdair Meredith. Now these two gentlemen are talking about specifically using allocators in
a more transparent way. They want to make a proposal that would come in probably C++ 23
or something like that where it would make using custom allocators a little bit more automatic;
but I took away from this an example of just how to use allocators that I had never seen
before. So I'm gonna put the compiler in C++ 17 mode here just to prove a point that this isn't
requiring C++ 20 or anything, this should be in any compiler that you have available
right now with C++ 17 or so; and we're going to use the Polymorphic Memory Resource (PMR).
I'm gonna just start with 'vector',
and I'm going to create a quick vector using a polymorphic memory resource here. So what we need
is a local memory, and this will be our buffer of data on the stack. I'm going to pre-initialize
this - I'm going to create it with 2048 bytes just as a number to have, and then I'm going
to be using things in the PMR resource namespace now; so this is the monotonic_buffer_resource and
I'm just going to call this my resource. It's going to take my stackBuf object,
and the size of the stackBuf. So that's just how many bytes this thing has. This is this
is a C-style array right here that I'm doing, but you could almost certainly do this just as easily
with a C++ array. Now I am going to actually use this polymorphic memory resource in a vector.
So I can pass it an initializer list and initialize it with six objects (or whatever),
and then I can pass it a link to this thing, and that's basically just how simple it is. Now we're
not gonna dig into the Compiler Explorer code here because it does generate quite a bit more
code than just using 'vector' would in this case; but what have we actually accomplished?
Let's walk through this. So I have created this memory here on the stack and then this
resource memory monotonic_buffer_resource object that is created with a pointer to this thing,
and now my vector is actually using this resource. So I'm currently using this as a vector of
integers that is... 4 times 6 is 24... I'm using 24 bytes of the 2048 that I had available. If I
were to go and create a second vector, these two vectors are sharing the same buffer,
so I can drastically reduce the amount of dynamic allocations that I am doing. This is a really,
from a programmers perspective, pretty straightforward and easy way of using
a custom allocator and reducing the number of allocations that you have. Now obviously you're
going to be wondering what exactly impact is this going to really have on my code, to which point I
am going to bring you to Quick Bench. This is quick-bench.com - if you have never been here,
it is definitely a useful tool. Now I wanted to test the actual impact on this so I am creating
lists here. This is a 'listOfthings' in a 'listOfthings', and these two lists have 20
elements in them separately so I've got (let me get those notes out of the way here) - I'm going
to run a benchmark that compares my polymorphic memory resource, PMR, with this list that creates
20 using this guy and the list that creates 20 not using anything special. Now this one
has to do 20 separate allocations - that is how standard list works. It must allocate
each item in a separate dynamic allocation. This one is going to do 1 stack allocation,
0 heap allocations, and then it's going to create these 20 objects in this memory buffer. So let's
just go ahead and run this benchmark and see what the difference is. We have to have a compiler and
standard library combination selected where it can find this correctly, and we run it,
and the websites going to in the back end run a benchmark for us and it'll show us the
results. Ok. So here we have it - our PRM list is 3.6 times faster than using a regular list;
and so let's try to make a comparison with how this compares with 'vector'. Now we're in GCC 9.2,
which I don't think can do any kind of heap elision here, so we're going to be still seeing
heap allocations with 'vector', and vector should always be our default. Let's see what happens.
Okay, so we can see that the vector here is really, really fast. It is 7.6 times faster
than PRM list, but now we can get the behavior of something like a list at speeds approaching
that of a vector. So let's go ahead and do one final test and see what we get if we can avoid
entirely the dynamic heap allocations for 'vector' by also using a PMR vector. Now I am going to..
You wouldn't do something like this in isolation like this, this is something that you would - the
entire point of this would be to reuse the same memory resources for multiple objects of probably
differing types, but I want to try to bring this home and say, "Okay, we've got multiple lists, or
multiple vectors, doing these things, and in some cases they get to share the same memory resource
and in other cases they're doing their own things." So I think this is a little bit perhaps
more realistic to code that we would actually be using, and it certainly will be more realistic to
the code that I will be using in my game project. All right, so when we've got multiple things
happening here, so the regular list is now, let's see, it's still 3½ times slower than (or 3½ times
faster than) regular lists, so we didn't gain a whole lot there even though we're creating two
of these in each one. The regular vector, let's see, the PMR vector is now 2.3 times faster than
regular vector. So yeah. So be aware of this. Don't even get me started on things like standard
'map'. One of the things that makes 'map' so incredibly slow in some use cases is the fact that
it also has to do a dynamic allocation for each node, and that's also true for 'unordered_map'.
So yeah - be aware of these PMP resources and check them out on CPP reference; and see how you
can utilize them in your code. I mean, depending on what you need to do, this can be an absolutely
incredibly significant performance difference with relatively little pain from the perspective of the
programmer because you don't have to specify the type of allocator that you're using here since
it's using the polymorphic one, which basically means is taking a pointer to a thing and making
a virtual function call every single time it needs to allocate memory. But hey, there's clearly a win
here, so check this out and thanks for watching this episode of C++ Weekly. I hope you learned
something about this and that you check out these PMR resources in your standard library.