// bdlma_multipoolallocator.h                                         -*-C++-*-
#ifndef INCLUDED_BDLMA_MULTIPOOLALLOCATOR
#define INCLUDED_BDLMA_MULTIPOOLALLOCATOR

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a memory-pooling allocator of heterogeneous block sizes.
//
//@CLASSES:
//  bdlma::MultipoolAllocator: allocator managing varying-size memory pools
//
//@SEE_ALSO: bdlma_pool, bdlma_multipool
//
//@DESCRIPTION: This component provides a general-purpose, managed allocator,
// 'bdlma::MultipoolAllocator', that implements the 'bdlma::ManagedAllocator'
// protocol and provides an allocator that maintains a configurable number of
// 'bdlma::Pool' objects, each dispensing maximally-aligned memory blocks of a
// unique size.  The 'bdlma::Pool' 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 blocks of sufficient size exists.  Both the 'release' method and the
// destructor of a 'bdlma::MultipoolAllocator' release all memory currently
// allocated via the object.
//..
//   ,-------------------------.
//  ( bdlma::MultipoolAllocator )
//   `-------------------------'
//                |         ctor/dtor
//                |         maxPooledBlockSize
//                |         numPools
//                |         reserveCapacity
//                V
//    ,-----------------------.
//   ( bdlma::ManagedAllocator )
//    `-----------------------'
//                |         release
//                V
//       ,----------------.
//      ( bslma::Allocator )
//       `----------------'
//                          allocate
//                          deallocate
//..
// The main difference between a 'bdlma::MultipoolAllocator' and a
// 'bdlma::Multipool' is that, very often, a 'bdlma::MultipoolAllocator' 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::Multipool'.
// However, since 'bslma::Allocator *' is widely used across BDE interfaces,
// 'bdlma::MultipoolAllocator' is more general purpose than 'bdlma::Multipool'.
//
///Configuration at Construction
///-----------------------------
// When creating a 'bdlma::MultipoolAllocator', 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
//:   blocks 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:
//:   o the unique growth strategy for all pools, or
//:   o (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.
//: 3 MAX BLOCKS PER CHUNK -- the maximum number of memory blocks within a
//:   chunk, specified as either:
//:     o the unique maximum-blocks-per-chunk value for all of the pools, or
//:     o an array of maximum-blocks-per-chunk values corresponding to each
//:       individual pool.
//:   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.
//: 4 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 is used (see
//:   'bslma_default').
//
// 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::MultipoolAllocator' 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::MultipoolAllocator'
/// - - - - - - - - - - - - - - - - - - - - - - -
// A 'bdlma::MultipoolAllocator' can be used to supply memory to node-based
// data structures such as 'bsl::set', 'bsl::list', and 'bsl::map'.  Suppose we
// are implementing a container of named graphs, where a graph is defined by a
// set of edges and a set of nodes.  The various fixed-sized nodes and edges
// can be efficiently allocated by a 'bdlma::MultipoolAllocator'.
//
// 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 an 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)
//  {
//  }
//..
// Next, the container for the collection of named graphs,
// 'my_NamedGraphContainer', is defined as follows:
//..
//  class my_NamedGraphContainer {
//      // This class stores a map that indexes graph names to graph objects.
//
//      // DATA
//      bsl::map<bsl::string, my_Graph> d_graphMap;  // map from graph name 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::MultipoolAllocator' and pass it
// to our 'my_NamedGraphContainer'.  Since we know that the maximum block size
// needed is 32 ('sizeof(my_Graph)'), we can calculate the number of pools
// needed by using the formula given 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:
//..
//  int main()
//  {
//      enum { k_NUM_POOLS = 3 };
//
//      bdlma::MultipoolAllocator multipoolAllocator(NUM_POOLS);
//
//      my_NamedGraphContainer container(&multipoolAllocator);
//  }
//..
//
///Example 2: Performance of a 'bdlma::MultipoolAllocator'
/// - - - - - - - - - - - - - - - - - - - - - - - - - - -
// A 'bdlma::MultipoolAllocator' can greatly improve efficiency when it is used
// to supply memory to node-based data structures that frequently both insert
// and remove nodes, while growing to significant size before being destroyed.
// The following experiment will illustrate the benefits of using a
// 'bdlma::MultipoolAllocator' under this scenario by comparing the following 3
// different allocator uses:
//
//: 1 Using the 'bslma::NewDeleteAllocator'.
//:
//: 2 Using a 'bdlma::MultipoolAllocator' as a substitute for the
//:   'bslma::NewDeleteAllocator'.
//:
//: 3 Exploiting the managed aspect of 'bdlma::MultipoolAllocator' by avoiding
//:   invocation of the destructor of the data structure, since the
//:   destruction of the allocator will automatically reclaim all memory.
//
// First, we create a test data structure that contains three 'bsl::list's.
// Each list holds a different type of object, where each type has a different
// size.  For simplicity, we create these different object types as different
// instantiations of a template class, parameterized on the object size:
//..
//  template <int OBJECT_SIZE>
//  class my_TestObject {
//
//      // DATA
//      char d_data[OBJECT_SIZE];
//  };
//..
// Again, for simplicity, the sizes of these objects are chosen to be 20, 40,
// and 80, instead of being parameterized as part of the test data structure:
//..
//  class my_TestDataStructure {
//
//      // PRIVATE TYPES
//      typedef my_TestObject<20> Obj1;
//      typedef my_TestObject<40> Obj2;
//      typedef my_TestObject<80> Obj3;
//
//      // DATA
//      bsl::list<Obj1> d_list1;
//      bsl::list<Obj2> d_list2;
//      bsl::list<Obj3> d_list3;
//..
// The test will consist of the following steps:
//
//: 1 Push back into 'd_list1', then 'd_list2', then 'd_list3'.
//: 2 Repeat #1.
//: 3 Pop front from 'd_list1', then 'd_list2', then 'd_list3'.
//
// The above 3 steps will be repeated 'n' times, where 'n' is a parameter
// specified by the user.  This process will both grow the list and incorporate
// a large number of node removals.  Note that nodes are removed from the front
// of the list to simulate a particular real-world usage, where nodes removed
// are rarely those recently added (this also removes the possibility of noise
// from potential optimizations with relinquishing memory blocks that are most
// recently allocated).
//..
//    public:
//      // CREATORS
//      my_TestDataStructure(bslma::Allocator *basicAllocator = 0);
//
//      // MANIPULATORS
//      void pop();
//
//      void push();
//  };
//
//  // CREATORS
//  my_TestDataStructure::my_TestDataStructure(
//                                            bslma::Allocator *basicAllocator)
//  : d_list1(basicAllocator)
//  , d_list2(basicAllocator)
//  , d_list3(basicAllocator)
//  {
//  }
//..
// The 'push' method will push into the 3 'bsl::list' objects managed by
// 'my_TestDataStructure' sequentially.  Similarly, the 'pop' method will pop
// from the lists sequentially:
//..
//  // MANIPULATORS
//  void my_TestDataStructure::push()
//  {
//      // Push to the back of the 3 lists.
//
//      d_list1.push_back(Obj1());
//      d_list2.push_back(Obj2());
//      d_list3.push_back(Obj3());
//  }
//
//  void my_TestDataStructure::pop()
//  {
//      // Pop from the front of the 3 lists.
//
//      d_list1.pop_front();
//      d_list2.pop_front();
//      d_list3.pop_front();
//  }
//..
// The above 'push' and 'pop' methods will allow us to evaluate the cost to add
// and remove nodes using different allocators.  To evaluate the cost of
// destruction (and hence deallocation of all allocated memory in the list
// objects), we supply a 'static' 'test' method within a 'my_TestUtil' class to
// create the test mechanism, run the test, and destroy the test mechanism.
//
// The 'test' method accepts a 'testLengthFactor' argument specified by the
// user to control the length of the test.  The effect of 'testLengthFactor' is
// shown below:
//..
//  testLengthFactor            test size           n      iterations
//  ----------------     ----------------     --------     ----------
//        4                  10^4 = 10000           1          10000
//                                                 10           1000
//                                                100            100
//                                               1000             10
//                                              10000              1
//
//        5                 10^5 = 100000           1         100000
//                                                 10          10000
//                                                100           1000
//                                               1000            100
//                                              10000             10
//                                             100000              1
//
//        6                10^6 = 1000000           1        1000000
//                                                 10         100000
//                                                100          10000
//                                               1000           1000
//                                              10000            100
//                                             100000             10
//                                            1000000              1
//
//   // ...
//..
// For each row of the specified 'testLengthFactor', a 'my_TestDataStructure'
// will be created "iterations" times, and each time the lists within the data
// structure will grow by invoking 'push' twice and 'pop' once.  Note that "n"
// measures the effect of insertion and removal of nodes, while "iterations"
// measures the effect of construction and destruction of entire lists of
// nodes.
//
// The 'test' method also accepts a 'bslma::Allocator *' to be used as the
// allocator used to construct the test mechanism and its internal lists:
//..
//  class my_TestUtil {
//
//    public:
//      // CLASS METHODS
//      static
//      void test(int testLengthFactor, bslma::Allocator *basicAllocator)
//      {
//          int n          = 1;
//          int iterations = 1;
//
//          for (int i = 0; i < testLengthFactor; ++i) {
//              iterations *= 10;
//          }
//
//          for (int i = 0; i <= testLengthFactor; ++i) {
//              bsls::Stopwatch timer;
//              timer.start();
//
//              for (int j = 0; j < iterations; ++j) {
//                  my_TestDataStructure tds(basicAllocator);
//
//                  // Testing cost of insertion and deletion.
//
//                  for (int k = 0; k < n; ++k) {
//                      tds.push();
//                      tds.push();
//                      tds.pop();
//                  }
//
//                  // Testing cost of destruction.
//              }
//
//              timer.stop();
//
//              printf("%d\t%d\t%d\t%1.6f\n", testLengthFactor,
//                                            n,
//                                            iterations,
//                                            timer.elapsedTime());
//
//              n          *= 10;
//              iterations /= 10;
//          }
//      }
//..
// Next, to fully test the benefit of being able to relinquish all allocated
// memory at once, we use the 'testManaged' method, which accepts only managed
// allocators.  Instead of creating the test mechanism on the stack, the test
// mechanism will be created on the heap.  After running the test, the
// 'release' method of the allocator will reclaim all outstanding allocations
// at once, eliminating the need to run the destructor of the test mechanism:
//..
//      static
//      void testManaged(int                      testLengthFactor,
//                       bdlma::ManagedAllocator *managedAllocator)
//      {
//          int n          = 1;
//          int iterations = 1;
//
//          for (int i = 0; i < testLengthFactor; ++i) {
//              iterations *= 10;
//          }
//
//          for (int i = 0; i <= testLengthFactor; ++i) {
//              bsls::Stopwatch timer;
//              timer.start();
//
//              for (int j = 0; j < iterations; ++j) {
//                  my_TestDataStructure *tmPtr = new(*managedAllocator)
//                                      my_TestDataStructure(managedAllocator);
//
//                  // Testing cost of insertion and deletion.
//
//                  for (int k = 0; k < n; ++k) {
//                      tmPtr->push();
//                      tmPtr->push();
//                      tmPtr->pop();
//                  }
//
//                  // Testing cost of destruction.
//
//                  managedAllocator->release();
//              }
//
//              timer.stop();
//
//              printf("%d\t%d\t%d\t%1.6f\n", testLengthFactor,
//                                            n,
//                                            iterations,
//                                            timer.elapsedTime());
//
//              n          *= 10;
//              iterations /= 10;
//          }
//      }
//  };
//..
// Finally, in main, we run the test with the different allocators and
// different allocator configurations based on command line arguments:
//..
//  {
//      int testLengthFactor = 5;
//      const int NUM_POOLS  = 10;
//
//      if (argc > 2) {
//          testLengthFactor = bsl::atoi(argv[2]);
//      }
//
//      char growth = 'g';
//      if (argc > 3) {
//          growth = argv[3][0];
//          if (growth != 'g' && growth != 'c') {
//              printf("[g]eometric or [c]onstant growth must be used\n");
//              return -1;
//          }
//      }
//
//      int maxChunkSize = 32;
//      if (argc > 4) {
//          maxChunkSize = bsl::atoi(argv[4]);
//          if (maxChunkSize <= 0) {
//              printf("maxChunkSize must be >= 1");
//          }
//      }
//
//      bsls::BlockGrowth::Strategy strategy = growth == 'g'
//                                          ? bsls::BlockGrowth::BSLS_GEOMETRIC
//                                          : bsls::BlockGrowth::BSLS_CONSTANT;
//
//      printf("\nNew Delete Allocator:\n\n");
//      {
//          bslma::Allocator *nda = bslma::NewDeleteAllocator::allocator(0);
//          my_TestUtil::test(testLengthFactor, nda);
//      }
//
//      printf("\nMultipool Allocator with [%c], [%d]:\n\n", growth,
//                                                               maxChunkSize);
//      {
//          bdlma::MultipoolAllocator ma(NUM_POOLS, strategy, maxChunkSize);
//          my_TestUtil::test(testLengthFactor, &ma);
//      }
//
//      printf("\nMultipool Allocator Managed with [%c], [%d]:\n\n", growth,
//                                                               maxChunkSize);
//      {
//          bdlma::MultipoolAllocator ma(NUM_POOLS, strategy, maxChunkSize);
//          my_TestUtil::testManaged(testLengthFactor, &ma);
//      }
//
//      return 0;
//  }
//..
// An excerpt of the results of the test running on IBM under optimized mode,
// using default constructed 'bdlma::MultipoolAllocator' parameters, is shown
// below:
//..
//  New Delete Allocator:
//
//  6       1       1000000 3.006253
//  6       10      100000  2.369734
//  6       100     10000   2.598567
//  6       1000    1000    2.604546
//  6       10000   100     2.760319
//  6       100000  10      3.085765
//  6       1000000 1       4.465030
//
//  Multipool Allocator with [g], [32]:
//
//  6       1       1000000 0.766064
//  6       10      100000  0.408509
//  6       100     10000   0.357019
//  6       1000    1000    0.436448
//  6       10000   100     0.643206
//  6       100000  10      0.932662
//  6       1000000 1       0.938906
//
//  Multipool Allocator Managed with [g], [32]:
//
//  6       1       1000000 1.958663
//  6       10      100000  0.463185
//  6       100     10000   0.371201
//  6       1000    1000    0.357816
//  6       10000   100     0.368082
//  6       100000  10      0.388422
//  6       1000000 1       0.529167
//..
// It is clear that using a 'bdlma::MultipoolAllocator' results in an
// improvement in memory allocation by a factor of about 4.  Furthermore, if
// the managed aspect of the multipool allocator is exploited, the cost of
// destruction rapidly decreases in relative terms as the list grows larger
// (increasing 'n').

#include <bdlscm_version.h>

#include <bdlma_managedallocator.h>
#include <bdlma_multipool.h>

#include <bslma_allocator.h>

#include <bsls_performancehint.h>
#include <bsls_types.h>

namespace BloombergLP {
namespace bdlma {

                         // ========================
                         // class MultipoolAllocator
                         // ========================

class MultipoolAllocator : public ManagedAllocator {
    // This class implements the 'bdlma::ManagedAllocator' protocol to provide
    // an allocator that maintains a configurable number of 'bdlma::Pool'
    // objects, each dispensing memory blocks of a unique size.  The
    // 'bdlma::Pool' objects are placed in an array, with each successive pool
    // managing memory blocks of size twice that of the previous pool.  Each
    // multipool allocation (deallocation) request allocates memory from
    // (returns memory to) the internal pool having the smallest block size not
    // less than the requested size, or, if no pool manages memory blocks of
    // sufficient sized, from a separately managed list of memory blocks.  Both
    // the 'release' method and the destructor of a 'bdlma::MultipoolAllocator'
    // release all memory currently allocated via the object.

    // DATA
    Multipool d_multipool;  // manager for allocated memory blocks

  private:
    // NOT IMPLEMENTED
    MultipoolAllocator(const MultipoolAllocator&);
    MultipoolAllocator& operator=(const MultipoolAllocator&);

  public:
    // CREATORS
    explicit
    MultipoolAllocator(bslma::Allocator *basicAllocator = 0);
    explicit
    MultipoolAllocator(int numPools, bslma::Allocator *basicAllocator = 0);
    explicit
    MultipoolAllocator(bsls::BlockGrowth::Strategy  growthStrategy,
                       bslma::Allocator            *basicAllocator = 0);
    MultipoolAllocator(int                          numPools,
                       bsls::BlockGrowth::Strategy  growthStrategy,
                       bslma::Allocator            *basicAllocator = 0);
    MultipoolAllocator(int                          numPools,
                       bsls::BlockGrowth::Strategy  growthStrategy,
                       int                          maxBlocksPerChunk,
                       bslma::Allocator            *basicAllocator = 0);
        // Create a multipool allocator.  Optionally specify 'numPools',
        // indicating the number of internally created 'bdlma::Pool' objects;
        // the block size of the first pool is 8 bytes, with the block size of
        // each additional pool successively doubling.  If 'numPools' is not
        // specified, an implementation-defined number of pools 'N' -- covering
        // memory blocks ranging in size from '2^3 = 8' to '2^(N+2)' -- are
        // created.  Optionally specify a 'growthStrategy' indicating whether
        // the number of blocks allocated at once for every internally created
        // 'bdlma::Pool' should be either fixed or grow geometrically, starting
        // with 1.  If 'growthStrategy' is not specified, the allocation
        // strategy for each internally created 'bdlma::Pool' object is
        // geometric, starting from 1.  If 'numPools' and 'growthStrategy' are
        // specified, optionally specify a 'maxBlocksPerChunk', indicating the
        // maximum number of blocks to be allocated at once when a pool must be
        // replenished.  If 'maxBlocksPerChunk' is not specified, an
        // implementation-defined value is used.  Optionally specify a
        // 'basicAllocator' used to supply memory.  If 'basicAllocator' is 0,
        // the currently installed default allocator is used.  Memory
        // allocation (and deallocation) requests will be satisfied using the
        // internally maintained pool managing memory blocks of the smallest
        // size not less than the requested size, or directly from the
        // underlying allocator (supplied at construction), if no internal pool
        // managing memory blocks of sufficient size exists.  The behavior is
        // undefined unless '1 <= numPools' and '1 <= maxBlocksPerChunk'.  Note
        // that, on platforms where
        // '8 < bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT', excess memory may be
        // allocated for pools managing smaller blocks.  Also note that
        // 'maxBlocksPerChunk' need not be an integral power of 2; if geometric
        // growth would exceed the maximum value, the chunk size is capped at
        // that value.

    MultipoolAllocator(int                                numPools,
                       const bsls::BlockGrowth::Strategy *growthStrategyArray,
                       bslma::Allocator                  *basicAllocator = 0);
    MultipoolAllocator(int                                numPools,
                       const bsls::BlockGrowth::Strategy *growthStrategyArray,
                       int                                maxBlocksPerChunk,
                       bslma::Allocator                  *basicAllocator = 0);
    MultipoolAllocator(int                          numPools,
                       bsls::BlockGrowth::Strategy  growthStrategy,
                       const int                   *maxBlocksPerChunkArray,
                       bslma::Allocator            *basicAllocator = 0);
    MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     const int                         *maxBlocksPerChunkArray,
                     bslma::Allocator                  *basicAllocator = 0);
        // Create a multipool allocator having the specified 'numPools',
        // indicating the number of internally created 'bdlma::Pool' objects;
        // the block size of the first pool is 8 bytes, with the block size of
        // each additional pool successively doubling.  Optionally specify a
        // 'growthStrategy' indicating whether the number of blocks allocated
        // at once for every internally created 'bdlma::Pool' should be either
        // fixed or grow geometrically, starting with 1.  If 'growthStrategy'
        // is not specified, optionally specify a 'growthStrategyArray',
        // indicating the strategies for each individual 'bdlma::Pool' created
        // by this object.  If neither 'growthStrategy' nor
        // 'growthStrategyArray' is specified, the allocation strategy for each
        // internally created 'bdlma::Pool' object will grow geometrically,
        // starting from 1.  Optionally specify a 'maxBlocksPerChunk',
        // indicating the maximum number of blocks to be allocated at once when
        // a pool must be replenished.  If 'maxBlocksPerChunk' is not
        // specified, optionally specify a 'maxBlocksPerChunkArray', indicating
        // the maximum number of blocks to allocate at once for each
        // individually created 'bdlma::Pool' object.  If neither
        // 'maxBlocksPerChunk' nor 'maxBlocksPerChunkArray' is specified, an
        // implementation-defined value is used.  Optionally specify a
        // 'basicAllocator' used to supply memory.  If 'basicAllocator' is 0,
        // the currently installed default allocator is used.  Memory
        // allocation (and deallocation) requests will be satisfied using the
        // internally maintained pool managing memory blocks of the smallest
        // size not less than the requested size, or directly from the
        // underlying allocator (supplied at construction), if no internal pool
        // managing memory blocks of sufficient size exists.  The behavior is
        // undefined unless '1 <= numPools', 'growthStrategyArray' has at least
        // 'numPools' strategies, '1 <= maxBlocksPerChunk', and
        // 'maxBlocksPerChunkArray' has at least 'numPools' positive values.
        // Note that, on platforms where
        // '8 < bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT', excess memory may be
        // allocated for pools managing smaller blocks.  Also note that the
        // maximum need not be an integral power of 2; if geometric growth
        // would exceed a maximum value, the chunk size is capped at that
        // value.

    virtual ~MultipoolAllocator();
        // Destroy this multipool allocator.  All memory allocated from this
        // allocator is released.

    // MANIPULATORS
    void reserveCapacity(bsls::Types::size_type size, int numObjects);
        // Reserve memory from this multipool allocator to satisfy memory
        // requests for at least the specified 'numObjects' having the
        // specified 'size' (in bytes) before the pool replenishes.  If 'size'
        // is 0, this method has no effect.  The behavior is undefined unless
        // 'size <= maxPooledBlockSize()' and '0 <= numObjects'.

                                // Virtual Functions

    virtual void *allocate(bsls::Types::size_type size);
        // Return the address of a contiguous block of maximally-aligned memory
        // of (at least) the specified 'size' (in bytes).  If 'size' is 0, no
        // memory is allocated and 0 is returned.  If
        // 'size > maxPooledBlockSize()', the memory allocation is managed
        // directly by the underlying allocator, but will not be pooled .

    virtual void deallocate(void *address);
        // Return the memory block at the specified 'address' back to this
        // allocator for reuse.  If 'address' is 0, this method has no effect.
        // The behavior is undefined unless 'address' was allocated by this
        // allocator, and has not already been deallocated.

    virtual void release();
        // Release all memory currently allocated through this multipool
        // allocator.

    // ACCESSORS
    int numPools() const;
        // Return the number of pools managed by this multipool allocator.

    bsls::Types::size_type maxPooledBlockSize() const;
        // Return the maximum size of memory blocks that are pooled by this
        // multipool allocator.  Note that the maximum value is defined as:
        //..
        //  2 ^ (numPools + 2)
        //..
        // where 'numPools' is either specified at construction, or an
        // implementation-defined value.
};

// ============================================================================
//                             INLINE DEFINITIONS
// ============================================================================

                         // ------------------------
                         // class MultipoolAllocator
                         // ------------------------

// CREATORS
inline
MultipoolAllocator::MultipoolAllocator(
                     bslma::Allocator                  *basicAllocator)
: d_multipool(basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(growthStrategy, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategy, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategyArray, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     int                                maxBlocksPerChunk,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategy, maxBlocksPerChunk, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     int                                maxBlocksPerChunk,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategyArray, maxBlocksPerChunk, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     const int                         *maxBlocksPerChunkArray,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategy, maxBlocksPerChunkArray, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     const int                         *maxBlocksPerChunkArray,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools,
              growthStrategyArray,
              maxBlocksPerChunkArray,
              basicAllocator)
{
}

// MANIPULATORS
inline
void *MultipoolAllocator::allocate(bsls::Types::size_type size)
{
    return d_multipool.allocate(size);
}

inline
void MultipoolAllocator::deallocate(void *address)
{
    if (BSLS_PERFORMANCEHINT_PREDICT_LIKELY(address != 0)) {
        d_multipool.deallocate(address);
    }
}

inline
void MultipoolAllocator::release()
{
    d_multipool.release();
}

inline
void MultipoolAllocator::reserveCapacity(bsls::Types::size_type size,
                                         int                    numObjects)
{
    d_multipool.reserveCapacity(size, numObjects);
}

// ACCESSORS
inline
int MultipoolAllocator::numPools() const
{
    return d_multipool.numPools();
}

inline
bsls::Types::size_type MultipoolAllocator::maxPooledBlockSize() const
{
    return d_multipool.maxPooledBlockSize();
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2016 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------