// bdlma_multipool.h -*-C++-*- #ifndef INCLUDED_BDLMA_MULTIPOOL #define INCLUDED_BDLMA_MULTIPOOL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a memory manager to manage pools of varying block sizes. // //@CLASSES: // bdlma::Multipool: memory manager that manages pools of varying block sizes // //@SEE_ALSO: bdlma_pool, bdlma_multipoolallocator // //@DESCRIPTION: This component implements a memory manager, 'bdlma::Multipool', // 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::Multipool' release all memory currently allocated via the object. // // A 'bdlma::Multipool' can be depicted visually: //.. // +-----+--- memory blocks of 8 bytes // | | // ======== ----- ----- ------------ // |8 bytes |---->| | | ... | // >========< =====^=====^============ // |16 bytes| // >========< \___________ __________/ // |32 bytes| V // >========< a "chunk" // | | // | ... | // | | // ======== // | // +------- array of 'bdlma::Pool' //.. // Note that a "chunk" is a large, contiguous block of memory, internal to a // 'bdlma::Pool' maintained by the multipool, from which memory blocks of // uniform size are dispensed to users. // ///Configuration at Construction ///----------------------------- // When creating a 'bdlma::Multipool', 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 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::Multipool' 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::Multipool' Directly /// - - - - - - - - - - - - - - - - - - - - - - - // A 'bdlma::Multipool' can be used by containers that hold different types of // elements, each of uniform size, for efficient memory allocation of new // elements. Suppose we have a factory class, 'my_MessageFactory', that // creates messages based on user requests. Each message is created with the // most efficient memory storage possible, using predefined 8-byte, 16-byte, // and 32-byte buffers. If the message size exceeds the three predefined // values, a generic message is used. For efficient memory allocation of // messages, we use a 'bdlma::Multipool'. // // First, we define our message types as follows: //.. // class my_MessageFactory; // // class my_Message { // // This class represents a general message interface that provides a // // 'getMessage' method for clients to retrieve the underlying message. // // public: // // ACCESSORS // virtual const char *getMessage() = 0; // // Return the null-terminated message string. // }; // // class my_SmallMessage : public my_Message { // // This class represents an 8-byte message (including null terminator). // // // DATA // char d_buffer[8]; // // // FRIEND // friend class my_MessageFactory; // // private: // // NOT IMPLEMENTED // my_SmallMessage(const my_SmallMessage&); // my_SmallMessage& operator=(const my_SmallMessage&); // // // PRIVATE CREATORS // my_SmallMessage(const char *msg, int length) // { // assert(length <= 7); // // bsl::memcpy(d_buffer, msg, length); // d_buffer[length] = '\0'; // } // // // PRIVATE ACCESSORS // virtual const char *getMessage() // { // return d_buffer; // } // }; // // class my_MediumMessage : public my_Message { // // This class represents a 16-byte message (including null // // terminator). // // // DATA // char d_buffer[16]; // // // FRIEND // friend class my_MessageFactory; // // private: // // NOT IMPLEMENTED // my_MediumMessage(const my_MediumMessage&); // my_MediumMessage& operator=(const my_MediumMessage&); // // // PRIVATE CREATORS // my_MediumMessage(const char *msg, int length) // { // assert(length <= 15); // // bsl::memcpy(d_buffer, msg, length); // d_buffer[length] = '\0'; // } // // // PRIVATE ACCESSORS // virtual const char *getMessage() // { // return d_buffer; // } // }; // // class my_LargeMessage : public my_Message { // // This class represents a 32-byte message (including null // // terminator). // // // DATA // char d_buffer[32]; // // // FRIEND // friend class my_MessageFactory; // // private: // // NOT IMPLEMENTED // my_LargeMessage(const my_LargeMessage&); // my_LargeMessage& operator=(const my_LargeMessage&); // // // PRIVATE CREATORS // my_LargeMessage(const char *msg, int length) // { // assert(length <= 31); // // bsl::memcpy(d_buffer, msg, length); // d_buffer[length] = '\0'; // } // // // PRIVATE ACCESSORS // virtual const char *getMessage() // { // return d_buffer; // } // }; // // class my_GenericMessage : public my_Message { // // This class represents a generic message. // // // DATA // char *d_buffer; // // // FRIEND // friend class my_MessageFactory; // // private: // // NOT IMPLEMENTED // my_GenericMessage(const my_GenericMessage&); // my_GenericMessage& operator=(const my_GenericMessage&); // // // PRIVATE CREATORS // my_GenericMessage(char *msg) : d_buffer(msg) // { // } // // // PRIVATE ACCESSORS // virtual const char *getMessage() // { // return d_buffer; // } // }; //.. // Then, we define our factory class, 'my_MessageFactory', as follows: //.. // class my_MessageFactory { // // This class implements an efficient message factory that builds and // // returns messages. The life-time of the messages created by this // // factory is the same as this factory. // // // DATA // bdlma::Multipool d_multipool; // multipool used to supply memory // // private: // // Not implemented: // my_MessageFactory(const my_MessageFactory&); // // public: // // CREATORS // my_MessageFactory(bslma::Allocator *basicAllocator = 0); // // Create a message factory. Optionally specify a 'basicAllocator' // // used to supply memory. If 'basicAllocator' is 0, the currently // // installed default allocator is used. // // ~my_MessageFactory(); // // Destroy this factory and reclaim all messages created by it. // // // MANIPULATORS // my_Message *createMessage(const char *data); // // Create a message storing the specified 'data'. The behavior is // // undefined unless 'data' is null-terminated. // // void disposeAllMessages(); // // Dispose of all created messages. // // void disposeMessage(my_Message *message); // // Dispose of the specified 'message'. The behavior is undefined // // unless 'message' was created by this factory, and has not // // already been disposed. // }; //.. // Next, we define the 'inline' methods of 'my_MessageFactory'. // // In calling the multipool's 'release' method, 'disposeAllMessages' quickly // deallocates all memory blocks that were used to create messages currently // outstanding from the factory. Following the call to 'release', *all* memory // that had been allocated from the multipool is available for reuse: //.. // // MANIPULATORS // inline // void my_MessageFactory::disposeAllMessages() // { // d_multipool.release(); // } //.. // Similarly, the call to the multipool's 'deleteObject' method in // 'disposeMessage' first destroys the message, then releases the memory that // had been allocated for it back to the multipool for use in creating another // message having the same size: //.. // inline // void my_MessageFactory::disposeMessage(my_Message *message) // { // d_multipool.deleteObject(message); // } //.. // A multipool optimizes the allocation of memory by using // dynamically-allocated buffers (also known as chunks) to supply memory. As // each chunk can satisfy multiple memory block requests before requiring // additional dynamic memory allocation, the number of dynamic allocation // requests needed is greatly reduced. // // For the number of pools managed by the multipool, we chose to use the // implementation-defined default value instead of calculating and specifying a // value. If users instead want to specify the number of pools, the value can // be calculated as the smallest value, 'N', such that the following // relationship holds: //.. // N > log2(sizeof(Object Type)) - 2 //.. // Next, we define the creators of 'my_MessageFactory': //.. // // CREATORS // my_MessageFactory::my_MessageFactory(bslma::Allocator *basicAllocator) // : d_multipool(basicAllocator) // { // } //.. // Note that in the destructor, all outstanding messages are reclaimed // automatically when 'd_multipool' is destroyed: //.. // my_MessageFactory::~my_MessageFactory() // { // } //.. // Finally, we define the 'createMessage' factory method that actually creates // the messages using memory provided by the multipool. A 'bdlma::Multipool' // is ideal for allocating the different sized messages since repeated // deallocations might be necessary, which renders a 'bdlma::SequentialPool' // unsuitable: //.. // // MANIPULATORS // my_Message *my_MessageFactory::createMessage(const char *data) // { // enum { k_SMALL = 8, k_MEDIUM = 16, k_LARGE = 32 }; // // const int length = static_cast<int>(bsl::strlen(data)); // // if (length < k_SMALL) { // return new(d_multipool.allocate(sizeof(my_SmallMessage))) // my_SmallMessage(data, length); // RETURN // } // // if (length < k_MEDIUM) { // return new(d_multipool.allocate(sizeof(my_MediumMessage))) // my_MediumMessage(data, length); // RETURN // } // // if (length < k_LARGE) { // return new(d_multipool.allocate(sizeof(my_LargeMessage))) // my_LargeMessage(data, length); // RETURN // } // // char *buffer = (char *)d_multipool.allocate(length + 1); // bsl::memcpy(buffer, data, length + 1); // // return new(d_multipool.allocate(sizeof(my_GenericMessage))) // my_GenericMessage(buffer); // } //.. // ///Example 2: Implementing an Allocator Using 'bdlma::Multipool' /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Suppose that we want to create a multipool allocator (i.e., that implements // the 'bslma::Allocator' interface) that allocates memory from multiple // 'bdlma::Pool' objects in a similar fashion to 'bdlma::Multipool'. In this // example, we create just such a multipool allocator, 'my_MultipoolAllocator', // that uses a 'bdlma::Multipool' to manage the multiple pools. // // First, we define the interface of 'my_MultipoolAllocator': //.. // class my_MultipoolAllocator : public bslma::Allocator { // // This class implements the 'bslma::Allocator' protocol to provide an // // allocator that manages a set of memory pools, each dispensing memory // // blocks of a unique size, with each successive pool's block size // // being twice that of the previous one. // // // DATA // bdlma::Multipool d_multiPool; // memory manager for allocated memory // // blocks // // public: // // CREATORS // my_MultipoolAllocator(bslma::Allocator *basicAllocator = 0); // // Create a multipool allocator. Optionally specify a // // 'basicAllocator' used to supply memory. If 'basicAllocator' is // // 0, the currently installed default allocator is used. // // // ... // // virtual ~my_MultipoolAllocator(); // // Destroy this multipool allocator. All memory allocated from // // this memory pool is released. // // // MANIPULATORS // 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. // // virtual void deallocate(void *address); // // Relinquish the memory block at the specified 'address' back to // // this multipool allocator for reuse. The behavior is undefined // // unless 'address' is non-zero, was allocated by this multipool // // allocator, and has not already been deallocated. // }; //.. // Note that the interface and documentation for this class is simplified for // this usage example. Please see 'bdlma_multipoolallocator' for a similar // class meant for production use. // // Finally, we provide the trivial implementation of 'my_MultipoolAllocator': //.. // // CREATORS // inline // my_MultipoolAllocator::my_MultipoolAllocator( // bslma::Allocator *basicAllocator) // : d_multiPool(basicAllocator) // { // } // // my_MultipoolAllocator::~my_MultipoolAllocator() // { // } // // // MANIPULATORS // inline // void *my_MultipoolAllocator::allocate(bsls::Types::size_type size) // { // if (0 == size) { // return 0; // RETURN // } // // return d_multiPool.allocate(size); // } // // inline // void my_MultipoolAllocator::deallocate(void *address) // { // d_multiPool.deallocate(address); // } //.. #include <bdlscm_version.h> #include <bdlma_blocklist.h> #include <bdlma_pool.h> #include <bslma_allocator.h> #include <bslma_deleterhelper.h> #include <bsls_alignmentutil.h> #include <bsls_blockgrowth.h> #include <bsls_types.h> namespace BloombergLP { namespace bdlma { // =============== // class Multipool // =============== class Multipool { // This class implements a memory manager 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 size, from a separately managed list // of memory blocks. Both the 'release' method and the destructor of a // 'bdlma::Multipool' release all memory currently allocated via the // object. // PRIVATE TYPES struct Header { // This 'struct' provides header information for each allocated memory // block. The header stores the index to the pool used for the memory // allocation. union { int d_poolIdx; // index to pool used for this // memory block, or -1 if from // 'd_blockList' bsls::AlignmentUtil::MaxAlignedType d_dummy; // force maximum alignment } d_header; }; // DATA Pool *d_pools_p; // array of memory pools, each // dispensing fixed-size memory // blocks int d_numPools; // number of memory pools bsls::Types::size_type d_maxBlockSize; // largest memory block size; // dispensed by the // 'd_numPools - 1'th pool; always // a power of 2 BlockList d_blockList; // memory manager for "large" // memory blocks bslma::Allocator *d_allocator_p; // holds (but does not own) // allocator private: // PRIVATE MANIPULATORS void initialize(bsls::BlockGrowth::Strategy growthStrategy, int maxBlocksPerChunk); void initialize(const bsls::BlockGrowth::Strategy *growthStrategyArray, int maxBlocksPerChunk); void initialize(bsls::BlockGrowth::Strategy growthStrategy, const int *maxBlocksPerChunkArray); void initialize(const bsls::BlockGrowth::Strategy *growthStrategyArray, const int *maxBlocksPerChunkArray); // Initialize this multipool with the specified 'growthStrategy[Array]' // and 'maxBlocksPerChunk[Array]'. If an array is used, each // individual 'bdlma::Pool' maintained by this multipool is initialized // with the corresponding growth strategy or max blocks per chunk entry // within the array. // PRIVATE ACCESSORS int findPool(bsls::Types::size_type size) const; // Return the index of the memory pool in this multipool for an // allocation request of the specified 'size' (in bytes). The behavior // is undefined unless 'size <= maxPooledBlockSize()'. Note that the // index of the memory pool managing memory blocks having the minimum // block size is 0. private: // NOT IMPLEMENTED Multipool(const Multipool&); Multipool& operator=(const Multipool&); public: // CREATORS explicit Multipool(bslma::Allocator *basicAllocator = 0); explicit Multipool(int numPools, bslma::Allocator *basicAllocator = 0); explicit Multipool(bsls::BlockGrowth::Strategy growthStrategy, bslma::Allocator *basicAllocator = 0); Multipool(int numPools, bsls::BlockGrowth::Strategy growthStrategy, bslma::Allocator *basicAllocator = 0); Multipool(int numPools, bsls::BlockGrowth::Strategy growthStrategy, int maxBlocksPerChunk, bslma::Allocator *basicAllocator = 0); // Create a multipool memory manager. 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. Multipool(int numPools, const bsls::BlockGrowth::Strategy *growthStrategyArray, bslma::Allocator *basicAllocator = 0); Multipool(int numPools, const bsls::BlockGrowth::Strategy *growthStrategyArray, int maxBlocksPerChunk, bslma::Allocator *basicAllocator = 0); Multipool(int numPools, bsls::BlockGrowth::Strategy growthStrategy, const int *maxBlocksPerChunkArray, bslma::Allocator *basicAllocator = 0); Multipool(int numPools, const bsls::BlockGrowth::Strategy *growthStrategyArray, const int *maxBlocksPerChunkArray, bslma::Allocator *basicAllocator = 0); // Create a multipool memory manager 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. ~Multipool(); // Destroy this multipool. All memory allocated from this memory pool // is released. // MANIPULATORS 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, and will not be pooled, but // will be deallocated when the 'release' method is called, or when // this object is destroyed. void deallocate(void *address); // Relinquish the memory block at the specified 'address' back to this // multipool object for reuse. The behavior is undefined unless // 'address' is non-zero, was allocated by this multipool object, and // has not already been deallocated. template <class TYPE> void deleteObject(const TYPE *object); // Destroy the specified 'object' based on its dynamic type and then // use this multipool object to deallocate its memory footprint. This // method has no effect if 'object' is 0. The behavior is undefined // unless 'object', when cast appropriately to 'void *', was allocated // using this multipool object and has not already been deallocated. // Note that 'dynamic_cast<void *>(object)' is applied if 'TYPE' is // polymorphic, and 'static_cast<void *>(object)' is applied otherwise. template <class TYPE> void deleteObjectRaw(const TYPE *object); // Destroy the specified 'object' and then use this multipool to // deallocate its memory footprint. This method has no effect if // 'object' is 0. The behavior is undefined unless 'object' is !not! a // secondary base class pointer (i.e., the address is (numerically) the // same as when it was originally dispensed by this multipool), was // allocated using this multipool, and has not already been // deallocated. void release(); // Relinquish all memory currently allocated via this multipool object. void reserveCapacity(bsls::Types::size_type size, int numBlocks); // Reserve memory from this multipool to satisfy memory requests for at // least the specified 'numBlocks' 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 <= numBlocks'. // ACCESSORS int numPools() const; // Return the number of pools managed by this multipool object. bsls::Types::size_type maxPooledBlockSize() const; // Return the maximum size of memory blocks that are pooled by this // multipool object. Note that the maximum value is defined as: //.. // 2 ^ (numPools + 2) //.. // where 'numPools' is either specified at construction, or an // implementation-defined value. // Aspects bslma::Allocator *allocator() const; // Return the allocator used by this object to allocate memory. Note // that this allocator can not be used to deallocate memory // allocated through this pool. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // --------------- // class Multipool // --------------- // MANIPULATORS template <class TYPE> inline void Multipool::deleteObject(const TYPE *object) { bslma::DeleterHelper::deleteObject(object, this); } template <class TYPE> inline void Multipool::deleteObjectRaw(const TYPE *object) { bslma::DeleterHelper::deleteObjectRaw(object, this); } // ACCESSORS inline int Multipool::numPools() const { return d_numPools; } inline bsls::Types::size_type Multipool::maxPooledBlockSize() const { return d_maxBlockSize; } // Aspects inline bslma::Allocator *Multipool::allocator() const { return d_allocator_p; } } // 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 ----------------------------------