3

When I run the next code in VS2017:

#include <boost/pool/pool_alloc.hpp>
#include <map>
#include <iostream>

int main()
{
    using Map = std::map<int, int, std::less<int>, boost::pool_allocator<std::pair<const int, int>>>;
    using Pool = boost::singleton_pool<boost::pool_allocator_tag, sizeof(Map)>;

    Map temp;

    for (int i = 1; i < 5; i++) temp[i] = i;

    std::cout << "First addresses:\n";
    for (auto& kv : temp) std::cout << &kv.second << "\n";

    temp.clear();
    Pool::purge_memory();

    Map temp2;

    for (int i = 1; i < 5; i++) temp2[i] = i;

    std::cout << "Second addresses:\n";
    for (auto& kv : temp2) std::cout << &kv.second << "\n";

    temp2.clear();
    Pool::purge_memory();

    return 0;
}

I get the output:

First addresses:
02A108F4
02A1090C
02A10924
02A1093C
Second addresses:
02A1090C
02A10924
02A1093C
02A10954

Live example

This behavior seems incorrect: what happened to address 02A108F4? It seems it was not returned to the pool during the purge.

This doesn't happen when I use a std::vector instead of a std::map. gcc also seems to return the memory correctly: Live example.

Is this a bug in VS2017?

JHBonarius
  • 10,824
  • 3
  • 22
  • 41

1 Answers1

2

You're assuming things about the implementation details of the pool. You may well be right there is a loss, but you can't conclude this from the allocation patterns you see.

Also, you're purging the memory for the pool allocator associated with sizeof(int). However, the value_type is already std::pair<int const, int>, and that leaves the fact that the map implementation allocates an unspecified node-type instead.

Oh, and the reason your allocator worked is precisely the same: the container implementation knows you cannot possibly provide the right type of allocator, since the allocated type is unspecified. Therefore it will always rebind to get the required type.

So, at least make it

Live On Rextester

#include <boost/pool/pool_alloc.hpp>
#include <map>
#include <iostream>

using Map = std::map<int, int, std::less<int>, boost::pool_allocator<int>>;
using Pool = boost::singleton_pool<boost::pool_allocator_tag, sizeof(Map::value_type)>;

void foo() {
    Map temp;

    for (int i = 1; i < 5; i++) temp[i] = i;

    std::cout << "First addresses:\n";
    for (auto& kv : temp) std::cout << &kv.second << "\n";
}

int main()
{
    foo();
    Pool::purge_memory();

    foo();
    Pool::purge_memory();
}

This, though is still assuming implementation details. I think c++17 gives you some more information to work with (http://en.cppreference.com/w/cpp/container/node_handle) or otherwise you could see wheter Boost Container has the relevant details: https://www.boost.org/doc/libs/1_51_0/doc/html/boost/container/map.html#id463544-bb

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I had just added a note about that in the answer. We keep being an our tails :) – sehe May 25 '18 at 12:53
  • Ah, but now in my example, where I have two maps `temp` and `temp2` ,the same issue occurs again. Even with your modifications. [Live example](http://rextester.com/PPVV44433). I'm not using delete, I'm using `purge`, so all memory should be returned, right? – JHBonarius May 25 '18 at 13:02
  • I think `std::map` stores extra stuff using the allocator when creating an object. But that only happens in VC++ and not in GCC it seems. – JHBonarius May 25 '18 at 13:10
  • I think you need to want the first sentence again. That was _aside_ from the obvious allocator size mismatch. See this: http://rextester.com/WIWYK11486 If you observe closely it's only the first element that gets a /slightly/ displaced address there. The other nodes coincide with the addresses of the first run. You can't really reason about this. If you must have the control, consider object_pool (avoiding the singleton pools) or intrusive containers e.g. – sehe May 25 '18 at 13:17
  • Re: "stores extra stuff" - that might be (although I don't think that is conformant?) and you could try to [disable debug iterators](https://msdn.microsoft.com/en-us/library/aa985982.aspx) or other runtime library extras – sehe May 25 '18 at 13:18
  • OK, but then back [to this question](https://stackoverflow.com/a/50508905/6717178), where I found this issue. How can I use the `pool_allocator` for an `object_pool`? – JHBonarius May 25 '18 at 13:20
  • 1
    @JHBonarius in that case I don't see the issue with std::map doing minor allocations on the side. If it does http://rextester.com/IFF93902 ? – sehe May 25 '18 at 14:06
  • 1
    I bumped into some extra information, from "the horses' mouth" explaining the lingering allocation after `clear()`, when not destructing the `map`: https://twitter.com/CoderCasey/status/1005578618578325506 (be sure to read the whole thread) – sehe Jun 09 '18 at 23:01