UPDATE
Per comments, answer, and additional research, I have come to the conclusion that there is typically no difference between a set
and a map
in terms of node overhead. My question that follows is really:
How do you determine node overhead for convenient use of
boost::pool_allocator
as a custom allocator?
And, a further update: The node overhead is probably never going to be more than the size of 4 pointers, so just purging the Boost Pool for sizeof(T)
, sizeof(T)+sizeof(int)
, sizeof(T) + 2*sizeof(int)
, sizeof(T) + 3*sizeof(int)
and sizeof(T) + 4*sizeof(int)
(or int64_t
for 64-bit systems) should be fine. That is what I am actually doing, and it works.
I want to use a boost memory pool to manage tens of millions of tiny, identically-sized objects by avoiding calls to the destructors of these objects, and instead freeing the memory in single swaths containing many instances per swath.
I posted another question about this issue, and the answer to that question has led me to understand that the question I really need to answer is the one I am asking here.
Consider the following code:
class Obj { // ... has an operator<() ... };
typedef std::set<Obj, std::less<Obj>, boost::fast_pool_allocator<Obj>> fast_set_obj;
// Deliberately do not use a managed pointer -
// I will *NOT* delete this object, but instead
// I will manage the memory using the memory pool!!!
fast_set_obj * mset = new fast_set_obj;
// ... add some Obj's to 'mset'
mset->insert(Obj());
mset->insert(Obj());
// Do something desireable with the set ...
...
// All done.
// It's time to release the memory, but do NOT call any Obj destructors.
// The following line of code works exactly as intended.
boost::singleton_pool<boost::fast_pool_allocator_tag, sizeof(Obj const)>::purge_memory();
If you step into the purge_memory()
function of the last line of code, you will see that the fast_pool_allocator
nicely proceeds to free the memory from the system, just as desired. (No Obj
destructors are called, because as noted in the linked question above, the job of the custom allocator is just to allocate and free memory, not to call constructors or destructors.)
It works precisely as desired. Great!
However, here is the problem. If you replace the set
with a map
, and then try to use the boost::pool_allocator
, nothing happens in the call to purge_memory()
!
typedef std::map<int, int, std::less<int>,
boost::fast_pool_allocator<std::pair<int const, int>>>
fast_map_obj;
// Ditto above: Deliberately do not use managed pointer
mast_map_obj * mmap = new fast_map_obj;
mmap[5] = Obj();
mmap[6] = Obj();
...
// Uh-oh. The following line of code DOES NOTHING, because I was using a map, not a set!
boost::singleton_pool<boost::fast_pool_allocator_tag,
sizeof(std::pair<int const, int>)>::purge_memory();
As noted, the last line of code does nothing. The reason is that the boost::fast_pool_allocator
is a singleton that only responds to, and manages memory for, objects of a given size that is fixed at compile-time. This is why the sizeof
argument is used in the expression that calls purge_memory()
- it tells the Boost Pool code which of the various different singleton memory pools to purge (assuming the requested one exists due to previously having been instantiated).
Unfortunately, because the memory pool selected to be purged is size-dependent, it is critical that the size of the internal objects managed (i.e., created and destroyed in memory allocated via calls to the custom allocator) is known. Sadly, for std::map
, the size of the internal objects managed by the map
is neither sizeof(Obj)
nor sizeof(std::pair<int const, Obj>)
.
My question is: How do you rigorously, and in a cross-platform way that works according to the C++11 standard, determine the size of the objects internally managed by std::map
for use with boost::fast_pool_allocator
?
Is this even possible?
The problem is that you don't know the internal type which
set
uses for nodes.While I haven't figured out how to determine this at compile time, you can write a tracing allocator which prints out the
sizeof
the node type whenallocate
is called, as in:And a little test program (I'm testing with sets of ints):
On my system, I get output of:
There isn't a truly cross platform way to deduce what you are after, since every map implementation is unhappy in its own way, but generally it is the map node that will be allocated in the pool.
This looks different for the different implementations of the standard library, so you code will have to
#ifdef
-ed for the different versions. With a fragility warning in place, here are the main ones for the g++/clang++/msc compilers and their std libs:Here are some useful links for finding the necessary defines: