1

I have a class that stores points in an n-dimensional space. It has an integer, n, for the number of dimensions, and a pointer into an array for the distance along each axis. The program it is in creates and destroys many of these as it goes. To prevent memory fragmentation I use memory pools. I have 'class pool' for the class itself and an 'array pool' for each length of array.

In handing creating these, the sequence would be: get a copy of the overall class from the first memory pool, using the dimensionality (n) the class is being constructed with determine the correct 'array pool' is, or if it does not exist, create it, get a pointer to the memory segment for the class, and then return the whole thing. Essentially, the class is always the same, what varies is the size of its internal array and which pool the internal array came from.

I can think of a couple of ways to handle this but the one that I am gravitating towards is to write a factory class which handles all of that.

Is this the appropriate pattern to choose? Is there a more preferred method?

James Matta
  • 1,562
  • 16
  • 37
  • When do you know which method is right? Does it depend on the ctor-arguments, on the exact class chosen, on the actual classes size, or on what? If it depends on the class, a custom class-allocator is the appropriate answer. If it depends on the first, a factory-function. BTW: If the class can be inferred from the ctor-argument-types, a factory-method is appropriate in either case. – Deduplicator Aug 22 '14 at 17:48
  • @Deduplicator What do you mean? The class is always the same nDPoint. As raw data is read in from disk an nDPoint needs to be created with an array of the appropriate size to hold the point. If the class was in charge of allocating itself (instead of needing pools) it would be something like nDPoint* point = new nDPoint(n); where n is the number of dimensions in the point that was just read in. – James Matta Aug 22 '14 at 17:53
  • I see. Some points to consider: You are potentially trading more external fragmentation and code-complexity for less internal fragmentation. Measure and compare. And if I see it right, you don't really need a factory-function, but a `mempool-new`. – Deduplicator Aug 22 '14 at 18:07
  • Well this is the process of measuring the pool option. I found that the code, with no memory pools just billions of point alloc and dealloc, worked very fast at first and then got slower and slower as it ran and the amount of memory that a resource monitor reported it as using kept increasing, despite no memory leaks apparent to me or valgrind, that lead me to think fragmentation, and the pools are really simply things, basically linked lists of large arrays (segmented appropriately) with a queue to reuse array segments that were pointed to by an nDPoint but had the nDPoint destroyed. – James Matta Aug 22 '14 at 18:13
  • That said, if overloading the new operator is what is needed then that is what I shall do. If you write that up as an answer I will accept it. – James Matta Aug 22 '14 at 18:15

1 Answers1

2

Memory-Pools are generally for fine-tuning memory-allocation.

Some common themes for pool-allocated objects:

  • The dtors are trivial.
    • Or there are few exceptions.
      • Or it can always be called the same way at least.
  • They will all need to be cleaned up at the same time.
  • They will need to be cleaned up in LIFO-order.
  • They are all the same type.
    • Or at least they have the same size.
      • Anyway, the same alignment-requirements hold.

The more of the above statements hold the more potential benefit there is too using memory-pools.

Now, if you want to allocate an object from a memory-pool (as opposed to its internal allocations), there are multiple options:

  • Use a pool-allocator:

    template<class pool, class... ARG> auto new(size_t s, pool& p, ARG&&... arg)
    -> typename std::enable_if<std::is_same<
        decltype(p.allocate(s, std::forward<ARG>(ard)...))
        , void*>>*
    { return p.allocate(s, std::forward<ARG>(ard)...); }
    
    // Used like this:
    auto p = new(mypool, optional_pool_args...) myclass(myargs...);
    

    You might want to provide a matching deallocator to support directly returning the memory back to the pool, on error or for any other circumstance...

    Also, nothing hinders you from using the same in the class.

    You could also make it an "allocator-aware container", like those in the standard-library.

  • Use a class-specific allocator:
    Just add the allocator to the class, see Operator overloading
  • Use a factory-function manually doing all the steps and using placement-new.
Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • Most of those conditions apply to the objects, only needing to be cleaned up at the same time or cleaned up in LIFO order do not hold. However, the objects fall into two categories, long lived (from the time of their creation to program exit), and short lived (cleaned up after a bit of processing has been done). So I can get away with recycling the memory used by the short term ones using a structure within the pool to keep track of the few de-allocated chunks there are at any time. Thanks for your well written answer. – James Matta Aug 22 '14 at 19:55