Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlma_concurrentmultipoolallocator
[Package bdlma]

Provide an allocator to manage pools of varying object sizes. More...

Namespaces

namespace  bdlma

Detailed Description

Outline
Purpose:
Provide an allocator to manage pools of varying object sizes.
Classes:
bdlma::ConcurrentMultipoolAllocator allocator managing varying size pools
See also:
Component bdlma_concurrentpool, Component bdlma_concurrentmultipool
Description:
This component provides an allocator, bdlma::ConcurrentMultipoolAllocator, that implements the bdlma::ManagedAllocator protocol and provides an allocator that maintains a configurable number of bdlma::ConcurrentPool objects, each dispensing memory blocks of a unique size. The bdlma::ConcurrentPool objects are placed in an array, starting at index 0, with each successive pool managing memory blocks of a size twice that of the previous pool. Each multipool allocation (deallocation) request allocates memory from (returns memory to) the internal pool managing memory blocks of the smallest size not less than the requested size, or else from a separately managed list of memory blocks, if no internal pool managing memory block of sufficient size exists. Both the release method and the destructor of a bdlma::ConcurrentMultipoolAllocator release all memory currently allocated via the object.
   ,-----------------------------------.
  ( bdlma::ConcurrentMultipoolAllocator )
   `-----------------------------------'
               |         ctor/dtor
               |         maxPooledBlockSize
               |         numPools
               |         reserveCapacity
               V
    ,-----------------------.
   ( bdlma::ManagedAllocator )
    `-----------------------'
               |         release
               V
       ,-----------------.
      (  bslma::Allocator )
       `-----------------'
                        allocate
                        deallocate
The main difference between a bdlma::ConcurrentMultipoolAllocator and a bdlma::ConcurrentMultipool is that, very often, a bdlma::ConcurrentMultipoolAllocator is managed through a bslma::Allocator pointer. Hence, every call to the allocate method invokes a virtual function call, which is slower than invoking the non-virtual allocate method on a bdlma::ConcurrentMultipool. However, since bslma::Allocator * is widely used across BDE interfaces, bdlma::ConcurrentMultipoolAllocator is more general purposed than a bdlma::ConcurrentMultipool.
Configuration at Construction:
When creating a bdlma::ConcurrentMultipoolAllocator, clients can optionally configure:
  1. NUMBER OF POOLS -- the number of internal pools (the block size managed by the first pool is eight bytes, with each successive pool managing block of a size twice that of the previous pool).
  2. GROWTH STRATEGY -- geometrically growing chunk size starting from 1 (in terms of the number of memory blocks per chunk), or fixed chunk size, specified as either:

    • the unique growth strategy for all pools, or
    • (if the number of pools is specified) an array of growth strategies corresponding to each individual pool
If the growth strategy is not specified, geometric growth is used for all
pools.

MAX BLOCKS PER CHUNK -- the maximum number of memory blocks within a chunk, specified as either:

If the maximum blocks per chunk is not specified, an
implementation-defined default value is used. Note that the maximum
blocks per chunk can be configured only if the number of pools is also
configured.

BASIC ALLOCATOR -- the allocator used to supply memory (to replenish an internal pool, or directly if the maximum block size is exceeded). If not specified, the currently installed default allocator (see bslma_default) is used.

A default-constructed multipool allocator has a relatively small, implementation-defined number of pools N with respective block sizes ranging from 2^3 = 8 to 2^(N+2). By default, the initial chunk size, (i.e., the number of blocks of a given size allocated at once to replenish a pool's memory) is 1, and each pool's chunk size grows geometrically until it reaches an implementation-defined maximum, at which it is capped. Finally, unless otherwise specified, all memory comes from the allocator that was the currently installed default allocator at the time the bdlma::ConcurrentMultipoolAllocator was created.
Using the various pooling options described above, we can configure the number of pools maintained, whether replenishment should be adaptive (i.e., geometric starting with 1) or fixed at a maximum chunk size, what that maximum chunk size should be (which need not be an integral power of 2), and the underlying allocator used to supply memory. Note that both GROWTH STRATEGY and MAX BLOCKS PER CHUNK can be specified separately either as a single value applying to all of the maintained pools, or as an array of values, with the elements applying to each individually maintained pool.
Usage:
This section illustrates intended use of this component.
Example 1: Using a bdlma::ConcurrentMultipoolAllocator:
A bdlma::ConcurrentMultipoolAllocator can be used to supply memory to node-based data structures such as bsl::set, bsl::list or bsl::map. Suppose we are implementing a container of named graphs data structure, where a graph is defined by a set of edges and nodes. The various fixed-sized nodes can be efficiently allocated by a bdlma::ConcurrentMultipoolAllocator.
First, the edge class, my_Edge, is defined as follows:
  class my_Node;

  class my_Edge {
      // This class represents an edge within a graph.  Both ends of the
      // edge must be connected to nodes.

      // DATA
      my_Node *d_first;   // first node
      my_Node *d_second;  // second node

      // ...

    public:
      // CREATORS
      my_Edge(my_Node *first, my_Node *second);
          // Create an edge that connects to the specified 'first' and
          // 'second' nodes.

      // ...
  };

  // CREATORS
  my_Edge::my_Edge(my_Node *first, my_Node *second)
  : d_first(first), d_second(second)
  {
  }
Then, the node class, my_Node, is defined as follows:
  class my_Node {
      // This class represents a node within a graph.  A node can be
      // connected to any number of edges.

      // DATA
      bsl::set<my_Edge *> d_edges;  // set of edges this node connects to

      // ...

    private:
      // Not implemented:
      my_Node(const my_Node&);

    public:
      // TRAITS
      BSLMF_NESTED_TRAIT_DECLARATION(my_Node, bslma::UsesBslmaAllocator);

      // CREATORS
      explicit my_Node(bslma::Allocator *basicAllocator = 0);
          // Create a node not connected to any other nodes.  Optionally
          // specify a 'basicAllocator' used to supply memory.  If
          // 'basicAllocator' is 0, the currently installed default allocator
          // is used.

      // ...
  };

  // CREATORS
  my_Node::my_Node(bslma::Allocator *basicAllocator)
  : d_edges(basicAllocator)
  {
  }
Then we define the graph class, my_Graph, as follows:
  class my_Graph {
      // This class represents a graph having sets of nodes and edges.

      // DATA
      bsl::set<my_Edge> d_edges;  // set of edges in this graph
      bsl::set<my_Node> d_nodes;  // set of nodes in this graph

      // ...

    private:
      // Not implemented:
      my_Graph(const my_Graph&);

    public:
      // TRAITS
      BSLMF_NESTED_TRAIT_DECLARATION(my_Graph, bslma::UsesBslmaAllocator);

      // CREATORS
      explicit my_Graph(bslma::Allocator *basicAllocator = 0);
          // Create an empty graph.  Optionally specify a 'basicAllocator'
          // used to supply memory.  If 'basicAllocator' is 0, the currently
          // installed default allocator is used.

      // ...
  };

  my_Graph::my_Graph(bslma::Allocator *basicAllocator)
  : d_edges(basicAllocator)
  , d_nodes(basicAllocator)
  {
  }
Then finally, the container for the collection of named graphs, my_NamedGraphContainer, is defined as follows:
  class my_NamedGraphContainer {
      // This class stores a map that index graph names to graph objects.

      // DATA
      bsl::map<bsl::string, my_Graph> d_graphMap;  // map from graph names to
                                                   // graph

    private:
      // Not implemented:
      my_NamedGraphContainer(const my_NamedGraphContainer&);

    public:
      // TRAITS
      BSLMF_NESTED_TRAIT_DECLARATION(my_NamedGraphContainer,
                                     bslma::UsesBslmaAllocator);

      // CREATORS
      explicit my_NamedGraphContainer(bslma::Allocator *basicAllocator = 0);
          // Create an empty named graph container.  Optionally specify a
          // 'basicAllocator' used to supply memory.  If 'basicAllocator' is
          // 0, the currently installed default allocator is used.

      // ...
  };

  // CREATORS
  my_NamedGraphContainer::my_NamedGraphContainer(
                                            bslma::Allocator *basicAllocator)
  : d_graphMap(basicAllocator)
  {
  }
Finally, in main, we can create a bdlma::ConcurrentMultipoolAllocator and pass it to our my_NamedGraphContainer. Since we know that the maximum block size needed is 32 (comes from sizeof(my_Graph)), we can calculate the number of pools needed by using the formula specified in the "configuration at construction" section:
  largestPoolSize < 2 ^ (N + 2).
When solved for the above equation, the smallest N that satisfies this relationship is 3:
  enum { k_NUM_POOLS = 3 };

  bdlma::ConcurrentMultipoolAllocator basicAllocator(k_NUM_POOLS);

  my_NamedGraphContainer container(&basicAllocator);