Skip to content
Snippets Groups Projects
poolallocator.hh 14.9 KiB
Newer Older
Markus Blatt's avatar
Markus Blatt committed
// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
#ifndef DUNE_COMMON_POOLALLOCATOR_HH
#define DUNE_COMMON_POOLALLOCATOR_HH
Markus Blatt's avatar
Markus Blatt committed

Oliver Sander's avatar
Oliver Sander committed
/** \file
 * \brief An stl-compliant pool allocator
 */

#include "lcm.hh"
Markus Blatt's avatar
Markus Blatt committed
#include <typeinfo>
Markus Blatt's avatar
Markus Blatt committed
#include <iostream>
#include <cassert>
Markus Blatt's avatar
 
Markus Blatt committed
#include <new>
Markus Blatt's avatar
Markus Blatt committed

#ifndef DOXYGEN
// forward declarations.
// we need to know the test function to declare it friend
template<std::size_t size, typename T>
struct testPoolMain;
Markus Blatt's avatar
Markus Blatt committed
namespace Dune
{

  template<typename T, std::size_t s>
  class Pool;

  template<typename T, std::size_t s>
  class PoolAllocator;

}

namespace std
{
  /*
     template<class T, std::size_t S>
     inline ostream& operator<<(ostream& os, Dune::Pool<T,S>& pool)
     {
     os<<"pool="<<&pool<<" allocated_="<<pool.allocated_;
     return os;
     }

     template<class T, std::size_t S>
     inline ostream& operator<<(ostream& os, Dune::PoolAllocator<T,S>& pool)
     {
     os<<pool.memoryPool_<<std::endl;
     return os;
     }
   */
Markus Blatt's avatar
Markus Blatt committed
  /**
   * @file
   * This file implements the classes Pool and PoolAllocator providing
   * memory allocation for objects in chunks.
Markus Blatt's avatar
Markus Blatt committed
   * @author Markus Blatt
   */
  /**
Christian Engwer's avatar
Christian Engwer committed
   * @addtogroup Allocators
Markus Blatt's avatar
Markus Blatt committed
   *
   * @{
   */
Markus Blatt's avatar
Markus Blatt committed

Markus Blatt's avatar
Markus Blatt committed
  /**
   * @brief A memory pool of objects.
   *
   * The memory for the objects is organized in chunks.
   * Each chunks is capable of holding a specified number of
   * objects. The allocated objects will be properly aligned
   * for fast access.
   * Deallocated objects are cached for reuse to prevent memory
   * fragmentation.
Markus Blatt's avatar
Markus Blatt committed
   * @warning If the size of the objects allocated is less than the
   * size of a pointer memory is wasted.
   * @warning Due to aligned issues at the number of bytes of the
   * alignment prerequisite (< 4 bytes) are wasted. This effect
Oliver Sander's avatar
Oliver Sander committed
   * becomes negligible for big sizes of chunkSize.
   *
   * \tparam T The type that is allocated by us.
   * \tparam s The size of a memory chunk in bytes.
Markus Blatt's avatar
Markus Blatt committed
   */
Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t s>
Markus Blatt's avatar
Markus Blatt committed
  class Pool
  {
    // make the test function friend
    friend struct ::testPoolMain<s,T>;
Markus Blatt's avatar
Markus Blatt committed

    //friend std::ostream& std::operator<<<>(std::ostream&,Pool<T,s>&);
    template< class, std::size_t > friend class PoolAllocator;
Markus Blatt's avatar
Markus Blatt committed
  private:

    /** @brief Reference to next free element. */
    struct Reference
    {
      Reference *next_;
    };

Markus Blatt's avatar
Markus Blatt committed
  public:
Markus Blatt's avatar
Markus Blatt committed

Markus Blatt's avatar
Markus Blatt committed
    /** @brief The type of object we allocate memory for. */
    typedef T MemberType;
    enum
    {
      /**
       * @brief The size of a union of Reference and MemberType.
       */
      unionSize = ((sizeof(MemberType) < sizeof(Reference)) ?
                   sizeof(Reference) : sizeof(MemberType)),

Markus Blatt's avatar
Markus Blatt committed
      /**
       * @brief Size requirement. At least one object has to
Markus Blatt's avatar
Markus Blatt committed
       * stored.
       */
      size = ((sizeof(MemberType) <= s && sizeof(Reference) <= s) ?
              s : unionSize),

      /**
       * @brief The alignment that suits both the MemberType and
       * the Reference (i.e. their least common multiple).
       */
      alignment = Lcm<AlignmentOf<MemberType>::value,AlignmentOf<Reference>::value>::value,
Markus Blatt's avatar
Markus Blatt committed

      /**
       * @brief The aligned size of the type.
       *
       * This size is bigger than sizeof of the type and a multiple of
       * the alignment requirement.
Markus Blatt's avatar
Markus Blatt committed
       */
      alignedSize = ((unionSize % alignment == 0) ?
                     unionSize :
                     ((unionSize / alignment + 1) * alignment)),
Markus Blatt's avatar
Markus Blatt committed

      /**
       * @brief The size of each chunk memory chunk.
       *
       * Will be adapted to be a multiple of the alignment plus
       * an offset to handle the case that the pointer to the memory
       * does not satisfy the alignment requirements.
       */
      chunkSize = ((size % alignment == 0) ?
                   size : ((size / alignment + 1)* alignment))
                  + alignment - 1,
Markus Blatt's avatar
Markus Blatt committed

Markus Blatt's avatar
Markus Blatt committed
      /**
       * @brief The number of element each chunk can hold.
       */
      elements = ((chunkSize - alignment + 1)/ alignedSize)
Markus Blatt's avatar
Markus Blatt committed
    };
Markus Blatt's avatar
Markus Blatt committed
  private:
Markus Blatt's avatar
Markus Blatt committed
    /** @brief Chunk of memory managed by the pool. */
Markus Blatt's avatar
Markus Blatt committed
    struct Chunk
    {
Markus Blatt's avatar
Markus Blatt committed

      //friend int testPool<s,T>();

Markus Blatt's avatar
Markus Blatt committed
      /** @brief The memory we hold. */
      char chunk_[chunkSize];

      /**
       * @brief Adress the first properly aligned
       * position in the chunk.
       */
Markus Blatt's avatar
Markus Blatt committed

      /** @brief The next element */
      Chunk *next_;

      /**
       * @brief Constructor.
       */
      Chunk()
      {
        // Make sure the alignment is correct!
        // long long should be 64bit safe!
        unsigned long long lmemory = reinterpret_cast<unsigned long long>(chunk_);
        if(lmemory % alignment != 0)
          lmemory = (lmemory / alignment + 1)
                    * alignment;
Markus Blatt's avatar
Markus Blatt committed

        memory_ = reinterpret_cast<char *>(lmemory);
Markus Blatt's avatar
Markus Blatt committed
      }
    };

  public:
    /** @brief Constructor. */
    inline Pool();
    /** @brief Destructor. */
    inline ~Pool();
    /**
     * @brief Get a new or recycled object
     * @return A pointer to the object memory.
     */
Markus Blatt's avatar
 
Markus Blatt committed
    inline void* allocate();
Markus Blatt's avatar
Markus Blatt committed
    /**
     * @brief Free an object.
     * @param o The pointer to memory block of the object.
     */
    inline void free(void* o);

    /**
     * @brief Print elements in pool for debugging.
     */
    inline void print(std::ostream& os);
Markus Blatt's avatar
Markus Blatt committed
  private:

    // Prevent Copying!
    Pool(const Pool<MemberType,s>&);
Markus Blatt's avatar
Markus Blatt committed
    void operator=(const Pool<MemberType,s>& pool) const;
    /** @brief Grow our pool.*/
    inline void grow();
Markus Blatt's avatar
Markus Blatt committed
    /** @brief The first free element. */
    Reference *head_;
    /** @brief Our memory chunks. */
    Chunk *chunks_;
    /* @brief The number of currently allocated elements. */
    //size_t allocated_;
Markus Blatt's avatar
Markus Blatt committed
  };

  /**
   * @brief An allocator managing a pool of objects for reuse.
   *
   * This allocator is specifically useful for small data types
   * where new and delete are too expensive.
   *
   * It uses a pool of memory chunks where the objects will be allocated.
   * This means that assuming that N objects fit into memory only every N-th
Oliver Sander's avatar
Oliver Sander committed
   * request for an object will result in memory allocation.
Markus Blatt's avatar
Markus Blatt committed
   * @warning It is not suitable
   * for the use in standard containers as it cannot allocate
Oliver Sander's avatar
Oliver Sander committed
   * arrays of arbitrary size
   *
   * \tparam T The type that will be allocated.
   * \tparam s The number of elements to fit into one memory chunk.
Markus Blatt's avatar
Markus Blatt committed
   */
Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t s>
Markus Blatt's avatar
Markus Blatt committed
  class PoolAllocator
  {
    //friend std::ostream& std::operator<<<>(std::ostream&,PoolAllocator<T,s>&);
Markus Blatt's avatar
Markus Blatt committed
  public:
    /**
     * @brief Type of the values we construct and allocate.
     */
    typedef T value_type;

    enum
    {
      /**
Oliver Sander's avatar
Oliver Sander committed
       * @brief The number of objects to fit into one memory chunk
Markus Blatt's avatar
Markus Blatt committed
       * allocated.
       */
Markus Blatt's avatar
Markus Blatt committed
    };

    /**
     * @brief The pointer type.
     */
    typedef T* pointer;

    /**
     * @brief The constant pointer type.
     */
    typedef const T* const_pointer;

    /**
     * @brief The reference type.
     */
    typedef T& reference;

    /**
     * @brief The constant reference type.
     */
    typedef const T& const_reference;

    /**
     * @brief The size type.
     */
    typedef std::size_t size_type;

    /**
     * @brief The difference_type.
     */
    typedef std::ptrdiff_t difference_type;

Markus Blatt's avatar
Markus Blatt committed
    /**
     * @brief Constructor.
     */
    inline PoolAllocator();

     * @brief Copy Constructor that does not copy the memory pool.
     */
    template<typename U, std::size_t u>
    inline PoolAllocator(const PoolAllocator<U,u>&)
    {
      // we allow copying but never copy the pool
      // to have a clear ownership of allocated pointers.
    }
    /// \brief Copy constructor that does not copy the memory pool.
    PoolAllocator(const PoolAllocator&)
    {
      // we allow copying but never copy the pool
      // to have a clear ownership of allocated pointers.
      // For this behaviour we have to implement
      // the copy constructor, because the default
      // one would copy the pool and deallocation
      // of it would break.
    }
Markus Blatt's avatar
Markus Blatt committed
    /**
     * @brief Allocates objects.
     * @param n The number of objects to allocate. Has to be one!
Markus Blatt's avatar
Markus Blatt committed
     * @param hint Ignored hint.
     * @return A pointer tp the allocated elements.
     */
    inline pointer allocate(std::size_t n, const_pointer hint=0);
Markus Blatt's avatar
Markus Blatt committed

    /**
     * @brief Free objects.
     *
Carsten Gräser's avatar
Carsten Gräser committed
     * Does not call the destructor!
     * @param n The number of objects to free. Has to be one!
Markus Blatt's avatar
Markus Blatt committed
     * @param p Pointer to the first object.
Markus Blatt's avatar
Markus Blatt committed
     */
    inline void deallocate(pointer p, std::size_t n);

    /**
     * @brief Construct an object.
     * @param p Pointer to the object.
     * @param value The value to initialize it to.
     */
    inline void construct(pointer p, const_reference value);

    /**
     * @brief Destroy an object without freeing memory.
     * @param p Pointer to the object.
     */
    inline void destroy(pointer p);

    /**
     * @brief Convert a reference to a pointer.
     */
Markus Blatt's avatar
Markus Blatt committed
    inline pointer  address(reference x) const { return &x; }
Markus Blatt's avatar
Markus Blatt committed


    /**
     * @brief Convert a reference to a pointer.
     */
Markus Blatt's avatar
Markus Blatt committed
    inline const_pointer address(const_reference x) const { return &x; }
Markus Blatt's avatar
Markus Blatt committed

    /**
     * @brief Not correctly implemented, yet!
     */
Markus Blatt's avatar
Markus Blatt committed
    inline int max_size() const throw(){ return 1;}
Markus Blatt's avatar
Markus Blatt committed

    /**
     * @brief Rebind the allocator to another type.
     */
    template<class U>
    struct rebind
    {
      typedef PoolAllocator<U,s> other;
    };

    /** @brief The type of the memory pool we use. */
    typedef Pool<T,size> PoolType;

Markus Blatt's avatar
Markus Blatt committed
  private:
    /**
     * @brief The underlying memory pool.
     */
    PoolType memoryPool_;
Markus Blatt's avatar
Markus Blatt committed
  };

  // specialization for void
  template <std::size_t s>
  class PoolAllocator<void,s>
  {
  public:
    typedef void*       pointer;
    typedef const void* const_pointer;
    // reference to void members are impossible.
    typedef void value_type;
    template <class U> struct rebind
    {
      typedef PoolAllocator<U,s> other;
    };
  };


  template<typename T1, std::size_t t1, typename T2, std::size_t t2>
  bool operator==(const PoolAllocator<T1,t1>&, const PoolAllocator<T2,t2>&)
  {
    return false;
  }


  template<typename T1, std::size_t t1, typename T2, std::size_t t2>
  bool operator!=(const PoolAllocator<T1,t1>&, const PoolAllocator<T2,t2>&)
  {
    return true;
  }

  template<typename T, std::size_t t1, std::size_t t2>
  bool operator==(const PoolAllocator<T,t1>& p1, const PoolAllocator<T,t2>& p2)
    return &p1==&p2;
  }


  template<typename T, std::size_t t1, std::size_t t2>
  bool operator!=(const PoolAllocator<T,t1>& p1, const PoolAllocator<T,t2>& p2)
    return &p1 != &p2;
  }

  template<typename T, std::size_t t1, std::size_t t2>
  bool operator==(const PoolAllocator<void,t1>&, const PoolAllocator<T,t2>&)
  {
    return false;
  }


  template<typename T, std::size_t t1, std::size_t t2>
  bool operator!=(const PoolAllocator<void,t1>&, const PoolAllocator<T,t2>&)
  {
    return true;
  }
  template<std::size_t t1, std::size_t t2>
  bool operator==(const PoolAllocator<void,t1>& p1, const PoolAllocator<void,t2>& p2)
    return &p1==&p2;
  }

  template<std::size_t t1, std::size_t t2>
  bool operator!=(const PoolAllocator<void,t1>& p1, const PoolAllocator<void,t2>& p2)
    return &p1!=&p2;
Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t S>
Markus Blatt's avatar
Markus Blatt committed
  inline Pool<T,S>::Pool()
    : head_(0), chunks_(0) //, allocated_(0)
Markus Blatt's avatar
Markus Blatt committed
  {
    static_assert(sizeof(T)<=unionSize, "Library Error: type T is too big");
    static_assert(sizeof(Reference)<=unionSize, "Library Error: type of referene is too big");
    static_assert(unionSize<=alignedSize, "Library Error: alignedSize too small");
    static_assert(sizeof(T)<=chunkSize, "Library Error: chunkSize must be able to hold at least one value");
    static_assert(sizeof(Reference)<=chunkSize, "Library Error: chunkSize must be able to hold at least one reference");
    static_assert((chunkSize - (alignment - 1)) % alignment == 0, "Library Error: compiler cannot calculate!");
    static_assert(elements>=1, "Library Error: we need to hold at least one element!");
    static_assert(elements*alignedSize<=chunkSize, "Library Error: aligned elements must fit into chuck!");
Markus Blatt's avatar
Markus Blatt committed
  }
Markus Blatt's avatar
Markus Blatt committed

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t S>
Markus Blatt's avatar
Markus Blatt committed
  inline Pool<T,S>::~Pool()
  {
    /*
       if(allocated_!=0)
       std::cerr<<"There are still "<<allocated_<<" allocated elements by the Pool<"<<typeid(T).name()<<","<<S<<"> "
               <<static_cast<void*>(this)<<"! This is a memory leak and might result in segfaults"
               <<std::endl;
Markus Blatt's avatar
Markus Blatt committed
    // delete the allocated chunks.
    Chunk *current=chunks_;
Markus Blatt's avatar
Markus Blatt committed
    {
      Chunk *tmp = current;
      current = current->next_;
      delete tmp;
    }
  }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t S>
  inline void Pool<T,S>::print(std::ostream& os)
  {
    Chunk* current=chunks_;
    while(current) {
      os<<current<<" ";
    os<<current<<" ";
Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t S>
Markus Blatt's avatar
Markus Blatt committed
  inline void Pool<T,S>::grow()
  {
    Chunk *newChunk = new Chunk;
    newChunk->next_ = chunks_;
    chunks_ = newChunk;

Markus Blatt's avatar
 
Markus Blatt committed
    char* start = chunks_->memory_;
    char* last  = &start[elements*alignedSize];
    Reference* ref = new (start) (Reference);

    // grow is only called if head==0,
    assert(!head_);

Markus Blatt's avatar
 
Markus Blatt committed
    head_ = ref;
Markus Blatt's avatar
Markus Blatt committed

Markus Blatt's avatar
 
Markus Blatt committed
    for(char* element=start+alignedSize; element<last; element=element+alignedSize) {
      Reference* next = new (element) (Reference);
      ref->next_ = next;
      ref = next;
Markus Blatt's avatar
Markus Blatt committed
    }
Markus Blatt's avatar
 
Markus Blatt committed
    ref->next_=0;
Markus Blatt's avatar
Markus Blatt committed
  }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t S>
Markus Blatt's avatar
Markus Blatt committed
  inline void Pool<T,S>::free(void* b)
  {
#ifndef NDEBUG
      Chunk* current=chunks_;
      while(current) {
        if(static_cast<void*>(&current->chunk_)<=b &&
           static_cast<void*>((&current->chunk_)+chunkSize)>b)
          break;
        current=current->next_;
      }
      if(!current)
        throw std::bad_alloc();
#endif
Markus Blatt's avatar
 
Markus Blatt committed
      Reference* freed = static_cast<Reference*>(b);
      freed->next_ = head_;
      head_ = freed;
      //--allocated_;
      std::cerr<< "Tried to free null pointer! "<<b<<std::endl;
Markus Blatt's avatar
Markus Blatt committed
  }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t S>
Markus Blatt's avatar
 
Markus Blatt committed
  inline void* Pool<T,S>::allocate()
Markus Blatt's avatar
Markus Blatt committed
  {
    if(!head_)
Markus Blatt's avatar
Markus Blatt committed
      grow();

    Reference* p = head_;
    head_ = p->next_;
    //++allocated_;
Markus Blatt's avatar
 
Markus Blatt committed
    return p;
Markus Blatt's avatar
Markus Blatt committed
  }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t s>
Markus Blatt's avatar
Markus Blatt committed
  inline PoolAllocator<T,s>::PoolAllocator()
  { }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t s>
  inline typename PoolAllocator<T,s>::pointer
  PoolAllocator<T,s>::allocate(std::size_t n, const_pointer)
Markus Blatt's avatar
Markus Blatt committed
  {
Markus Blatt's avatar
 
Markus Blatt committed
    if(n==1)
      return static_cast<T*>(memoryPool_.allocate());
    else
      throw std::bad_alloc();
Markus Blatt's avatar
Markus Blatt committed
  }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t s>
Christian Engwer's avatar
Christian Engwer committed
  inline void PoolAllocator<T,s>::deallocate(pointer p, std::size_t n)
Markus Blatt's avatar
Markus Blatt committed
  {
    for(size_t i=0; i<n; i++)
      memoryPool_.free(p++);
Markus Blatt's avatar
Markus Blatt committed
  }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t s>
  inline void PoolAllocator<T,s>::construct(pointer p, const_reference value)
Markus Blatt's avatar
Markus Blatt committed
  {
    ::new (static_cast<void*>(p))T(value);
Markus Blatt's avatar
Markus Blatt committed
  }

Markus Blatt's avatar
Markus Blatt committed
  template<class T, std::size_t s>
Christian Engwer's avatar
Christian Engwer committed
  inline void PoolAllocator<T,s>::destroy(pointer p)
Markus Blatt's avatar
Markus Blatt committed
  {
    p->~T();
  }
Markus Blatt's avatar
Markus Blatt committed
  /** @} */
}
#endif