Provide a memory allocator that counts allocated bytes.
More...
Namespaces |
namespace | bdlma |
Detailed Description
- Outline
-
-
- Purpose:
- Provide a memory allocator that counts allocated bytes.
-
- Classes:
-
- See also:
- Component bslma_allocator, Component bslma_testallocator
-
- Description:
- This component provides a special-purpose counting allocator,
bdlma::CountingAllocator
, that implements the bslma::Allocator
protocol and provides instrumentation to track: (1) the number of bytes currently in use (numBytesInUse
), and (2) the cumulative number of bytes that have ever been allocated (numBytesTotal
). The accumulated statistics are based solely on the number of bytes requested in calls to the allocate
method. A print
method is provided to output the current state of the allocator's byte counts to a specified bsl::ostream
: ,------------------------.
( bdlma::CountingAllocator )
`------------------------'
| ctor/dtor
| numBytesInUse
| numBytesTotal
| name
| print
V
,----------------.
( bslma::Allocator )
`----------------'
allocate
deallocate
Like many other allocators, bdlma::CountingAllocator
relies on the currently installed default allocator (see bslma_default
) at construction. Clients may, however, override this allocator by supplying (at construction) any other allocator implementing the bslma::Allocator
protocol provided that it is fully thread-safe.
- Note that a
bdlma::CountingAllocator
necessarily incurs some overhead in order to provide its byte-counting functionality. However, this overhead is substantially less than that incurred by the bslma::TestAllocator
(see bslma_testallocator
), which keeps track of the same two statistics that are maintained by a bdlma::CountingAllocator
. Consequently, use of a bdlma::CountingAllocator
may be appropriate in cases where the overhead of bslma::TestAllocator
is too onerous. In particular, a counting allocator may be suitable even for production use in certain situations, whereas the test allocator is not intended for production use under any circumstance.
-
- Byte Counts:
- The two byte counts maintained by
bdlma::CountingAllocator
are initialized to 0 at construction and increased with each call to allocate
by size
, i.e., by the actual number of bytes requested. Each call to deallocate
decreases the numBytesInUse
count by the same amount by which the byte count was increased in the original allocate
call. The number of bytes currently in use is returned by numBytesInUse
and the total number of bytes ever allocated is returned by numBytesTotal
.
-
- Thread Safety:
- The
bdlma::CountingAllocator
class is fully thread-safe (see bsldoc_glossary
) provided that the underlying allocator (established at construction) is fully thread-safe.
-
- Usage:
- This section illustrates intended use of this component.
-
- Example 1: Tracking a Container's Dynamic Memory Use:
- In this example, we demonstrate how a counting allocator may be used to track the amount of dynamic memory used by a container. The container used for illustration is
DoubleStack
, a stack of out-of-place double
values.
- First, we show the interface of the
DoubleStack
class:
class DoubleStack {
double **d_stack_p;
int d_capacity;
int d_length;
bslma::Allocator *d_allocator_p;
DoubleStack(const DoubleStack&);
DoubleStack& operator=(const DoubleStack&);
private:
void increaseCapacity();
public:
explicit
DoubleStack(bslma::Allocator *basicAllocator = 0);
~DoubleStack();
void push(double value);
void pop();
};
Next, we show the (elided) implementation of DoubleStack
.
- The default constructor creates a stack having the capacity for one element (the implementation of the destructor is not shown):
enum { k_INITIAL_CAPACITY = 1, k_GROWTH_FACTOR = 2 };
DoubleStack::DoubleStack(bslma::Allocator *basicAllocator)
: d_stack_p(0)
, d_capacity(k_INITIAL_CAPACITY)
, d_length(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
d_stack_p = (double **)
d_allocator_p->allocate(d_capacity * sizeof *d_stack_p);
}
The push
method first ensures that the array has sufficient capacity to accommodate an additional value, then allocates a block in which to store that value:
void DoubleStack::push(double value)
{
if (d_length >= d_capacity) {
increaseCapacity();
}
double *stackValue = (double *)d_allocator_p->allocate(sizeof(double));
*stackValue = value;
d_stack_p[d_length] = stackValue;
++d_length;
}
The pop
method asserts that the stack is not empty before deallocating the block used to store the element at the top of the stack: void DoubleStack::pop()
{
BSLS_ASSERT(0 < d_length);
d_allocator_p->deallocate(d_stack_p[d_length - 1]);
--d_length;
}
The push
method (above) made use of the private increaseCapacity
method, which, in turn, makes use of the reallocate
helper function (static
to the .cpp
file). Note that increaseCapacity
(below) increases the capacity of the double *
array by a factor of 2 each time that it is called:
static
void reallocate(double ***array,
int newCapacity,
int length,
bslma::Allocator *allocator)
{
BSLS_ASSERT(newCapacity > length);
double **tmp = *array;
*array = (double **)allocator->allocate(newCapacity * sizeof **array);
bsl::memcpy(*array, tmp, length * sizeof **array);
allocator->deallocate(tmp);
}
void DoubleStack::increaseCapacity()
{
const int newCapacity = d_capacity * k_GROWTH_FACTOR;
reallocate(&d_stack_p, newCapacity, d_length, d_allocator_p);
d_capacity = newCapacity;
}
Now, we are ready to employ a CountingAllocator
to illustrate the dynamic memory use of DoubleStack
. We first define two constants that facilitate portability of this example across 32- and 64-bit platforms: const int DBLSZ = sizeof(double);
const int PTRSZ = sizeof(double *);
First, we define a CountingAllocator
, ca
. At construction, a counting allocator can be configured with an optional name and an optional allocator. In this case, we give ca
a name to distinguish it from other counting allocators, but settle for using the default allocator: Next, we create a DoubleStack
, supplying it with ca
, and assert the expected memory use incurred by the default constructor: DoubleStack stack(&ca);
assert(1 * PTRSZ == ca.numBytesInUse());
assert(1 * PTRSZ == ca.numBytesTotal());
Next, we push an element onto the stack. The first push incurs an additional allocation to store (out-of-place) the value being inserted: stack.push(1.54); assert(1 * PTRSZ + 1 * DBLSZ == ca.numBytesInUse());
assert(1 * PTRSZ + 1 * DBLSZ == ca.numBytesTotal());
Next, we push a second element onto the stack. In this case, two allocations result, one due to the resizing of the internal array and one required to store the new value out-of-place: stack.push(0.99); assert(2 * PTRSZ + 2 * DBLSZ == ca.numBytesInUse());
assert(3 * PTRSZ + 2 * DBLSZ == ca.numBytesTotal());
Next, we pop the top-most element from the stack. The number of bytes in use decreases by the amount used to store the popped element out-of-place: stack.pop(); assert(2 * PTRSZ + 1 * DBLSZ == ca.numBytesInUse());
assert(3 * PTRSZ + 2 * DBLSZ == ca.numBytesTotal());
Finally, we print the state of ca
to standard output: which displays the following on a 32-bit platform: ----------------------------------------
Counting Allocator State
----------------------------------------
Allocator name: 'DoubleStack' Allocator
Bytes in use: 16
Bytes in total: 28