4

My C++ class builds a tree structure over time. Each node in the tree is currently allocated on construction (using new). The node class uses only a few bytes of memory. As the tree grows there may be 100,000s of nodes; the maximum number of nodes is not known on construction of the tree, other than the theoretical maximum of 2^33. I reference nodes in the tree structure by their pointer. All nodes are deallocated on destruction of the tree, and only then.

I'm after a Standard Library container or memory allocator/pool that I can use to allocate and store the nodes within my tree class, in order to reduce memory fragmentation and memory allocation overhead. I'd like to avoid having to write a custom allocator. The container should have the following two properties:

  1. Allocated objects do not move in memory, therefore can referenced by pointers safely.
  2. The class allocates memory for large blocks of objects, thus reducing memory fragmentation. Note that I do not require the entire container to be contiguous in memory.

I do not need the iterator or lookup functionality of the container, as my tree structure stores the pointers. What Standard Library class will provide me with this functionality, and give me the lowest memory overhead?

user664303
  • 2,053
  • 3
  • 13
  • 30

5 Answers5

11

Since you're asking specifically for a standard container, std::deque is the most promising option given your requirements. As long as you only add elements, the existing ones are not relocated, and references/pointers (but not iterators) remain valid. When removing elements, you may however need to leave gaps or swap the element to remove with the last element.

std::vector is not stable, and std::list, std::forward_list as well as all the associative containers are fragmented.

Looking at Boost.Container, you have additional options, however with other trade-offs:

  • boost::flat_map provides contiguous storage (like std::vector), but with it the stability problem
  • boost::stable_vector offers element stability at the cost of contiguity.

Alternatively, you can have a look at pool allocators (like Boost.Pool). They provide low fragmentation and fast allocation, and the container in front of it can still be used like a normal container.

TheOperator
  • 5,936
  • 29
  • 42
  • I don't mind _some_ fragmentation of memory, and don't need a contiguous container. However, I do want memory for objects allocated in chunks far larger than just the few bytes of a single node. – user664303 Oct 22 '15 at 10:34
  • Thank you. This is a useful and thorough overview. – user664303 Oct 22 '15 at 13:57
4

As you reference nodes in the tree structure by pointers (so, they shouldn't be reallocated) and wants to reduce memory fragmentation, I would recommend to use memory pool for Node objects.

Boost.Pool library may fit your needs.

Example:

class Tree
{
    Tree() : nodesPool_(new boost::object_pool<Node>()) {}

    void CreateNode(nodeArg1, nodeArg2, ...)
    {
        Node * node = nodesPool_->construct(nodeArg1, nodeArg2, ...);
        ...
    }

    std::unique_ptr<boost::object_pool<Node>> nodesPool_;
};
Stas
  • 11,571
  • 9
  • 40
  • 58
  • Yes, I want to use a memory pool. What stl memory pools are there? Perhaps that is a better question. – user664303 Oct 22 '15 at 09:50
  • @user664303: It's not "stl". The Standard Library does not have memory pools. Use Boost.Pool, as instructed. – Lightness Races in Orbit Oct 22 '15 at 09:58
  • Hmm, ideally after a Standard Library solution. – user664303 Oct 22 '15 at 09:59
  • There are no pool allocators in the standard library. Only [helpers](http://en.cppreference.com/w/cpp/header/memory) that let you build your own -- which is something I wouldn't recommend, given the wide range of optimized pools out there. – TheOperator Oct 22 '15 at 10:02
  • From the Boost.Pool docs, it's not clear to me how to use it to replace a call to new with a non-default constructor, i.e. one with input arguments. Unless I missed it. – user664303 Oct 22 '15 at 10:15
  • 1
    @user664303 See `construct()` method of `boost::object_pool`. It will be something like `boost::object_pool pool; pool.construct(nodeArg1, nodeArg2, ...)` – Stas Oct 22 '15 at 10:37
  • 2
    Even though this isn't a STL solution, I used it. But I used this pool instead of Boost's: http://www.codeproject.com/Articles/746630/O-Object-Pool-in-Cplusplus – user664303 Oct 22 '15 at 13:55
  • @user664303 Yes, looks good. Boost may be too big dependency if it is not already used in the project. I also, usually prefer small self-contained libraries. – Stas Oct 22 '15 at 14:01
  • Downvote because title explicitly asks for STL container. – Paul Oct 23 '15 at 07:53
  • @Paul kidding? There is no such stuff in STL. At the same time, a lot of useful libraries were implemented in Boost first, and then became part of STL. The point is to solve the problem rather then be a verbal critic. Using `std::deque` for this scenario just because it is a part of a standard is terrible idea. I would rather write my own object pool, it is not that complex. – Stas Oct 23 '15 at 07:58
  • @Stas, no not kidding. I realise that a lot of good things have come out of Boost and that some of the new stuff that went into the standard used Boost as incubator. My problem is that Boost is a _huge_ interwoven library, and even though ppl claim you can just use the parts you need, I have yet to see an example. Just saying that STL does not support it, but have a look at how it could be used to implement your own pool would be a good solution (and what actually happened), then link to Boost as an _alternative/example_. Boost is not in the standard and IMO this is not just verbal critisism. – Paul Oct 23 '15 at 08:41
  • 1
    @Paul I said he needs to use memory pool. Boost.Pool is just an example of library that can be used. I didn't insist on Boost, and he finally chose another 3rdParty library. – Stas Oct 23 '15 at 09:19
  • @Stas reread your answer, and it can be read that way. I stand by my statements, but will remove downvote (as soon as SO lets me) – Paul Oct 23 '15 at 09:28
2

What you described sounds exactly like what std::deque does. Also check out this article that compares vector vs deque

smac89
  • 39,374
  • 15
  • 132
  • 179
  • 1
    It seems that the std::deque chunk size is implementation dependent and not user-definable: http://stackoverflow.com/questions/24482767/how-to-control-the-chunk-size-of-stddeque-when-allocating-a-new-chunk – user664303 Oct 22 '15 at 10:24
  • Therefore it is not guaranteed that std::deque will offer me any benefits over new. I will be compiling this code for OS X, android, iOS, and Windows using the standard compilers for those platforms. I want something that will reliably allocate the objects in large chunks. – user664303 Oct 22 '15 at 10:27
  • `std::deque` may move objects (and invalidate pointers) the same way as `std::vector`. It is not safe to use in this scenario. – Stas Oct 22 '15 at 10:38
  • @Stas: Only if using the destructive members of the class, which I won't. So I believe it is safe for this particular scenario. But it is something to be careful of. – user664303 Oct 22 '15 at 10:41
  • References I've found for deque block sizes in MSVC and GCC are 16 and 512 bytes respectively. I'd prefer something around the 4K mark. But as I said, it's not user-definable. – user664303 Oct 22 '15 at 10:50
  • 1
    @user664303 agree, but there are so many ifs in this solution. If boost (or other 3rdParty lib) is not an option, I would implement some custom memory pool. – Stas Oct 22 '15 at 10:53
  • Deque chunk sizes listed here: http://info.prelert.com/blog/stl-container-memory-usage – user664303 Oct 22 '15 at 11:06
  • @user664303 from the looks of it, if you use clang, you might be able to get that 4k mark you are looking for. Or you can define a custom allocator to do this – smac89 Oct 22 '15 at 11:23
0

I'm afraid you're putting to many constraints on what you want to make it possible.

What you might try to use is vector. It is (apart from array and possibly someone else that I'm missing) the only standard container that will likely allocate the objects in one chunk.

However there's a catch here, when the vector is growing there's no guarantee that the objects will remain on the same address any more. If you don't resize the vector (or uses an array which can't grow) it's a good possibility that you're safe.

Otherwise the normal procedure is to write a custom allocator that uses a pool of objects to fulfill allocations. In addition you have to take care that your tree doesn't allocate a data block for each of the nodes anyway.

skyking
  • 13,817
  • 1
  • 35
  • 57
  • There are only two constraints. Lots of containers meet the first, but vector doesn't. The real question is which containers that meet the first criterion (e.g. deque, map, list, forward_list) can also be made to reliably allocate memory for new objects in chunks or blocks, whilst I add objects to the container one at a time. – user664303 Oct 22 '15 at 10:30
  • @user664303 It's not the count that complicates it. It's that you want it to be placed in a standard container and want to avoid custom allocator. If you want control over the allocations you would need to take control over it - the standard containers provide little guarantee for what allocations they perform. – skyking Oct 22 '15 at 11:23
0

Another option you might want to consider is using a std::vector, but keeping an index instead of a pointer on your nodes. That way you can even gain some memory, as you could store a 32 bits index instead of a 64 bits pointer, depending on your architecture and needs.

It requires that the nodes can be copied or moved of course.

Adrien Hamelin
  • 395
  • 3
  • 9