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?
map
nodes do not storpair
s. – Unanswerablemap
when it calls the custom allocator is the size ofstd::pair<K const, V>
, while I think it is true that the size of objects allocated internally byset
when it calls the custom allocator is the size ofK const
. – Unanswerablestd::map
are extremely intricate. Perhaps you can understand it more quickly? – Unanswerablerebind
member to create a new allocator that works for the types used in the implementation. Note that yourset
ormap
itself will not be allocated in the pool: only the internal storage. – Constructionstd::set<int>
for which the node size is the size of anint
(see Nevin's answer); but for more complex types, there is additional node overhead. I was wrong (and you're almost certainly right) that there's 'typically' no difference in node storage overhead between aset
and amap
- I was fooled because theset
I was testing with storedints
. – Unanswerableint
's, I would think it would work just as well for any other POD to use a sorted array and avoid the node overhead (the C++11 standard notwithstanding)... I wonder if it tests for the traitis_pod
? – Unanswerable