std.experimental.allocator

High-level interface for allocators. Implements bundled allocation/creation and destruction/deallocation of data including structs and classes, and also array primitives related to allocation. This module is the entry point for both making use of allocators and for their documentation.

Synopsis:

// Allocate an int, initialize it with 42
int* p = theAllocator.make!int(42);
assert(*p == 42);
// Destroy and deallocate it
theAllocator.dispose(p);

// Allocate using the global process allocator
p = processAllocator.make!int(100);
assert(*p == 100);
// Destroy and deallocate
processAllocator.dispose(p);

// Create an array of 50 doubles initialized to -1.0
double[] arr = theAllocator.makeArray!double(50, -1.0);
// Append two zeros to it
theAllocator.expandArray(arr, 2, 0.0);
// On second thought, take that back
theAllocator.shrinkArray(arr, 2);
// Destroy and deallocate
theAllocator.dispose(arr);

Layered Structure

D's allocators have a layered structure in both implementation and documentation:

  1. A high-level, dynamically-typed layer (described further down in this

    module). It consists of an interface called IAllocator, which concrete allocators need to implement. The interface primitives themselves are oblivious to the type of the objects being allocated; they only deal in void[], by necessity of the interface being dynamic (as opposed to type-parameterized). Each thread has a current allocator it uses by default, which is a thread-local variable theAllocator of type IAllocator. The process has a global allocator called processAllocator, also of type IAllocator. When a new thread is created, processAllocator is copied into theAllocator. An application can change the objects to which these references point. By default, at application startup, processAllocator refers to an object that uses D's garbage collected heap. This layer also include high-level functions such as make and dispose that comfortably allocate/create and respectively destroy/deallocate objects. This layer is all needed for most casual uses of allocation primitives.

  2. A mid-level, statically-typed layer for assembling several allocators into

    one. It uses properties of the type of the objects being created to route allocation requests to possibly specialized allocators. This layer is relatively thin and implemented and documented in the std.experimental.allocator.typed module. It allows an interested user to e.g. use different allocators for arrays versus fixed-sized objects, to the end of better overall performance.

  3. A low-level collection of highly generic heap building blocks

    Lego-like pieces that can be used to assemble application-specific allocators. The real allocation smarts are occurring at this level. This layer is of interest to advanced applications that want to configure their own allocators. A good illustration of typical uses of these building blocks is module std.experimental.allocator.showcase which defines a collection of frequently- used preassembled allocator objects. The implementation and documentation entry point is std.experimental.allocator.building_blocks. By design, the primitives of the static interface have the same signatures as the IAllocator primitives but are for the most part optional and driven by static introspection. The parameterized class CAllocatorImpl offers an immediate and useful means to package a static low-level allocator into an implementation of IAllocator.

  4. Core allocator objects that interface with D's garbage collected heap

    (std.experimental.allocator.gc_allocator), the C malloc family (std.experimental.allocator.mallocator), and the OS (std.experimental.allocator.mmap_allocator). Most custom allocators would ultimately obtain memory from one of these core allocators.

Idiomatic Use of std.experimental.allocator

As of this time, std.experimental.allocator is not integrated with D's built-in operators that allocate memory, such as new, array literals, or array concatenation operators. That means std.experimental.allocator is opt-in—applications need to make explicit use of it.

For casual creation and disposal of dynamically-allocated objects, use make, dispose, and the array-specific functions makeArray,

expandArray, and shrinkArray. These use by default D's garbage

collected heap, but open the application to better configuration options. These primitives work either with theAllocator but also with any allocator obtained by combining heap building blocks. For example:

---- void fun(size_t n) { // Use the current allocator int[] a1 = theAllocator.makeArray!int(n); scope(exit) theAllocator.dispose(a1); ... } ----

To experiment with alternative allocators, set theAllocator for the current thread. For example, consider an application that allocates many 8-byte objects. These are not well supported by the default allocator, so a

free list allocator would be recommended.

To install one in main, the application would use:

---- void main() { import std.experimental.allocator.building_blocks.free_list : FreeList; theAllocator = allocatorObject(FreeList!8()); ... } ----

Saving the IAllocator Reference For Later Use

As with any global resource, setting theAllocator and processAllocator should not be done often and casually. In particular, allocating memory with one allocator and deallocating with another causes undefined behavior. Typically, these variables are set during application initialization phase and last through the application.

To avoid this, long-lived objects that need to perform allocations, reallocations, and deallocations relatively often may want to store a reference to the allocator object they use throughout their lifetime. Then, instead of using theAllocator for internal allocation-related tasks, they'd use the internally held reference. For example, consider a user-defined hash table:

---- struct HashTable { private IAllocator allocator; this(size_t buckets, IAllocator allocator = theAllocator) { this.allocator = allocator; ... } // Getter and setter IAllocator allocator() { return allocator; } void allocator(IAllocator a) { assert(empty); allocator = a; } } ----

Following initialization, the HashTable object would consistently use its allocator object for acquiring memory. Furthermore, setting HashTable.allocator to point to a different allocator should be legal but only if the object is empty; otherwise, the object wouldn't be able to deallocate its existing state.

Using Allocators without IAllocator

Allocators assembled from the heap building blocks don't need to go through IAllocator to be usable. They have the same primitives as IAllocator and they work with make, makeArray, dispose etc. So it suffice to create allocator objects wherever fit and use them appropriately:

---- void fun(size_t n) { // Use a stack-installed allocator for up to 64KB StackFront!65536 myAllocator; int[] a2 = myAllocator.makeArray!int(n); scope(exit) myAllocator.dispose(a2); ... } ----

In this case, myAllocator does not obey the IAllocator interface, but implements its primitives so it can work with makeArray by means of duck typing.

One important thing to note about this setup is that statically-typed assembled allocators are almost always faster than allocators that go through IAllocator. An important rule of thumb is: "assemble allocator first, adapt to IAllocator after". A good allocator implements intricate logic by means of template assembly, and gets wrapped with IAllocator (usually by means of

allocatorObject) only once, at client level.

Types 9

interfaceIAllocator

Dynamic allocator interface. Code that defines allocators ultimately implements this interface. This should be used wherever a uniform type is required for encapsulating various allocator implementations.

Composition of allocators is not recommended at this level due to inflexibility of dynamic interfaces and inefficiencies caused by cascaded multiple calls. Instead, compose allocators using the static interface defined in std.experimental.allocator.building_blocks, then adapt the composed allocator to IAllocator (possibly by using CAllocatorImpl below).

Methods returning Ternary return Ternary.yes upon success, Ternary.no upon failure, and Ternary.unknown if the primitive is not implemented by the allocator instance.

Methods
uint alignment() @propertyReturns the alignment offered.
size_t goodAllocSize(size_t s)Returns the good allocation size that guarantees zero internal fragmentation.
void[] allocate(size_t, TypeInfo ti = null)Allocates `n` bytes of memory.
void[] alignedAllocate(size_t n, uint a)Allocates `n` bytes of memory with specified alignment `a`. Implementations that do not support this primitive should always return `null`.
void[] allocateAll()Allocates and returns all memory available to this allocator. Implementations that do not support this primitive should always return `null`.
bool expand(ref void[], size_t)Expands a memory block in place and returns `true` if successful. Implementations that don't support this primitive should always return `false`.
bool reallocate(ref void[], size_t)Reallocates a memory block.
bool alignedReallocate(ref void[] b, size_t size, uint alignment)Reallocates a memory block with specified alignment.
Ternary owns(void[] b)Returns `Ternary.yes` if the allocator owns `b`, `Ternary.no` if the allocator doesn't own `b`, and `Ternary.unknown` if ownership cannot be determined. Implementations that don't support this prim...
Ternary resolveInternalPointer(const void * p, ref void[] result)Resolves an internal pointer to the full block allocated. Implementations that don't support this primitive should always return `Ternary.unknown`.
bool deallocate(void[] b)Deallocates a memory block. Implementations that don't support this primitive should always return `false`. A simple way to check that an allocator supports deallocation is to call `deallocate(null)`.
bool deallocateAll()Deallocates all memory. Implementations that don't support this primitive should always return `false`.
Ternary empty()Returns `Ternary.yes` if no memory is currently allocated from this allocator, `Ternary.no` if some allocations are currently active, or `Ternary.unknown` if not supported.
void incRef() @safe @nogc pureIncreases the reference count of the concrete class that implements this interface.
bool decRef() @safe @nogc pureDecreases the reference count of the concrete class that implements this interface. When the reference count is `0`, the object self-destructs.

A reference counted struct that wraps the dynamic allocator interface. This should be used wherever a uniform type is required for encapsulating various allocator implementations.

Code that defines allocators ultimately implements the IAllocator interface, possibly by using CAllocatorImpl below, and then build a RCIAllocator out of this.

Composition of allocators is not recommended at this level due to inflexibility of dynamic interfaces and inefficiencies caused by cascaded multiple calls. Instead, compose allocators using the static interface defined in std_experimental_allocator_building_blocks.html, std.experimental.allocator.building_blocks, then adapt the composed allocator to RCIAllocator (possibly by using allocatorObject below).

Fields
private IAllocator _alloc
Methods
@nogc pure @safe auto ref opAssign()(typeof(this) rhs)
bool isNull(this _)() @nogc pure @safe
uint alignment() @property
size_t goodAllocSize(size_t s)
void[] allocate(size_t n, TypeInfo ti = null)
void[] alignedAllocate(size_t n, uint a)
void[] allocateAll()
bool expand(ref void[] b, size_t size)
bool reallocate(ref void[] b, size_t size)
bool alignedReallocate(ref void[] b, size_t size, uint alignment)
Ternary owns(void[] b)
Ternary resolveInternalPointer(const void * p, ref void[] result)
bool deallocate(void[] b)
Constructors
Destructors

Dynamic shared allocator interface. Code that defines allocators shareable across threads ultimately implements this interface. This should be used wherever a uniform type is required for encapsulating various allocator implementations.

Composition of allocators is not recommended at this level due to inflexibility of dynamic interfaces and inefficiencies caused by cascaded multiple calls. Instead, compose allocators using the static interface defined in std.experimental.allocator.building_blocks, then adapt the composed allocator to ISharedAllocator (possibly by using CSharedAllocatorImpl below).

Methods returning Ternary return Ternary.yes upon success, Ternary.no upon failure, and Ternary.unknown if the primitive is not implemented by the allocator instance.

Methods
uint alignment() @property shared;Returns the alignment offered.
size_t goodAllocSize(size_t s) shared;Returns the good allocation size that guarantees zero internal fragmentation.
void[] allocate(size_t, TypeInfo ti = null) shared;Allocates `n` bytes of memory.
void[] alignedAllocate(size_t n, uint a) shared;Allocates `n` bytes of memory with specified alignment `a`. Implementations that do not support this primitive should always return `null`.
void[] allocateAll() shared;Allocates and returns all memory available to this allocator. Implementations that do not support this primitive should always return `null`.
bool expand(ref void[], size_t) shared;Expands a memory block in place and returns `true` if successful. Implementations that don't support this primitive should always return `false`.
bool reallocate(ref void[], size_t) shared;Reallocates a memory block.
bool alignedReallocate(ref void[] b, size_t size, uint alignment) shared;Reallocates a memory block with specified alignment.
Ternary owns(void[] b) shared;Returns `Ternary.yes` if the allocator owns `b`, `Ternary.no` if the allocator doesn't own `b`, and `Ternary.unknown` if ownership cannot be determined. Implementations that don't support this prim...
Ternary resolveInternalPointer(const void * p, ref void[] result) shared;Resolves an internal pointer to the full block allocated. Implementations that don't support this primitive should always return `Ternary.unknown`.
bool deallocate(void[] b) shared;Deallocates a memory block. Implementations that don't support this primitive should always return `false`. A simple way to check that an allocator supports deallocation is to call `deallocate(null)`.
bool deallocateAll() shared;Deallocates all memory. Implementations that don't support this primitive should always return `false`.
Ternary empty() shared;Returns `Ternary.yes` if no memory is currently allocated from this allocator, `Ternary.no` if some allocations are currently active, or `Ternary.unknown` if not supported.
void incRef() @safe @nogc pure shared;Increases the reference count of the concrete class that implements this interface.
bool decRef() @safe @nogc pure shared;Decreases the reference count of the concrete class that implements this interface. When the reference count is `0`, the object self-destructs.

A reference counted struct that wraps the dynamic shared allocator interface. This should be used wherever a uniform type is required for encapsulating various allocator implementations.

Code that defines allocators shareable across threads ultimately implements the

ISharedAllocator interface, possibly by using CSharedAllocatorImpl below, and then build a RCISharedAllocator out

of this.

Composition of allocators is not recommended at this level due to inflexibility of dynamic interfaces and inefficiencies caused by cascaded multiple calls. Instead, compose allocators using the static interface defined in std_experimental_allocator_building_blocks.html, std.experimental.allocator.building_blocks, then adapt the composed allocator to RCISharedAllocator (possibly by using sharedAllocatorObject below).

Fields
private ISharedAllocator _alloc
Methods
@nogc pure @safe auto ref opAssign()(RCISharedAllocator rhs)
bool isNull(this _)() @nogc pure @safe
uint alignment() @property
size_t goodAllocSize(size_t s)
void[] allocate(size_t n, TypeInfo ti = null)
void[] alignedAllocate(size_t n, uint a)
void[] allocateAll()
bool expand(ref void[] b, size_t size)
bool reallocate(ref void[] b, size_t size)
bool alignedReallocate(ref void[] b, size_t size, uint alignment)
Ternary owns(void[] b)
Ternary resolveInternalPointer(const void * p, ref void[] result)
bool deallocate(void[] b)
Constructors
this(shared ISharedAllocator alloc)
Destructors
classCAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) : IAllocator

Implementation of IAllocator using Allocator. This adapts a statically-built allocator type to IAllocator that is directly usable by non-templated code.

Usually CAllocatorImpl is used indirectly by calling theAllocator.

Methods
uint alignment() @propertyReturns `impl.alignment`.
size_t goodAllocSize(size_t s)Returns `impl.goodAllocSize(s)`.
void[] allocate(size_t s, TypeInfo ti = null)Returns `impl.allocate(s)`.
void[] alignedAllocate(size_t s, uint a)If `impl.alignedAllocate` exists, calls it and returns the result. Otherwise, always returns `null`.
Ternary owns(void[] b)If `Allocator` implements `owns`, forwards to it. Otherwise, returns `Ternary.unknown`.
bool expand(ref void[] b, size_t s)Returns impl.expand(b) if defined, `false` otherwise.
bool reallocate(ref void[] b, size_t s)Returns impl.reallocate(b).
bool alignedReallocate(ref void[] b, size_t s, uint a)Forwards to `impl.alignedReallocate` if defined, `false` otherwise.
Ternary resolveInternalPointer(const void * p, ref void[] result)
bool deallocate(void[] b)If `impl.deallocate` is not defined, returns `false`. Otherwise it forwards the call.
bool deallocateAll()Calls `impl.deallocateAll()` and returns the result if defined, otherwise returns `false`.
Ternary empty()Forwards to `impl.empty()` if defined, otherwise returns `Ternary.unknown`.
void[] allocateAll()Returns `impl.allocateAll()` if present, `null` otherwise.
void incRef() @nogc nothrow pure @safe
bool decRef() @nogc nothrow pure @trusted
classCSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) : ISharedAllocator

Implementation of ISharedAllocator using Allocator. This adapts a statically-built, shareable across threads, allocator type to ISharedAllocator that is directly usable by non-templated code.

Usually CSharedAllocatorImpl is used indirectly by calling

processAllocator.
Methods
uint alignment() @property sharedReturns `impl.alignment`.
size_t goodAllocSize(size_t s) sharedReturns `impl.goodAllocSize(s)`.
void[] allocate(size_t s, TypeInfo ti = null) sharedReturns `impl.allocate(s)`.
void[] alignedAllocate(size_t s, uint a) sharedIf `impl.alignedAllocate` exists, calls it and returns the result. Otherwise, always returns `null`.
Ternary owns(void[] b) sharedIf `Allocator` implements `owns`, forwards to it. Otherwise, returns `Ternary.unknown`.
bool expand(ref void[] b, size_t s) sharedReturns impl.expand(b) if defined, `false` otherwise.
bool reallocate(ref void[] b, size_t s) sharedReturns impl.reallocate(b).
bool alignedReallocate(ref void[] b, size_t s, uint a) sharedForwards to `impl.alignedReallocate` if defined, `false` otherwise.
Ternary resolveInternalPointer(const void * p, ref void[] result) shared
bool deallocate(void[] b) sharedIf `impl.deallocate` is not defined, returns `false`. Otherwise it forwards the call.
bool deallocateAll() sharedCalls `impl.deallocateAll()` and returns the result if defined, otherwise returns `false`.
Ternary empty() sharedForwards to `impl.empty()` if defined, otherwise returns `Ternary.unknown`.
void[] allocateAll() sharedReturns `impl.allocateAll()` if present, `null` otherwise.
void incRef() @nogc nothrow pure @safe shared
bool decRef() @nogc nothrow pure @trusted shared
structThreadLocal(A)

Stores an allocator object in thread-local storage (i.e. non-shared D global). ThreadLocal!A is a subtype of A so it appears to implement A's allocator primitives.

A must hold state, otherwise ThreadLocal!A refuses instantiation. This means e.g. ThreadLocal!Mallocator does not work because Mallocator's state is not stored as members of Mallocator, but instead is hidden in the C library implementation.

Fields
A instanceThe allocator instance.
Constructors
this()`ThreadLocal` disables all constructors. The intended usage is `ThreadLocal!A.instance`.
private structEmbeddedTree(T, alias less)
Fields
private Node * root
Methods
private Node * insert(Node * n, ref Node * backref)
Node * find(Node * data)
Node * insert(Node * data)
Node * remove(Node * data)
private void remove(Node * n, Node * parent)
Ternary empty() const
void dump()
void dump(Node * r, uint indent)
void assertSane()
Nested Templates
Node
Fields
private Tree blockMap
Methods
void[] allocate(size_t bytes)Allocator API.
bool deallocate(void[] b)Ditto
Ternary owns(void[] b)Ditto
Ternary empty()Ditto
Ternary resolveInternalPointer(const void * p, ref void[] result) pure nothrow @safe @nogcReturns the block inside which `p` resides, or `null` if the pointer does not belong.

Functions 26

private fnRCIAllocator setupThreadAllocator() @nogc nothrow @safe ref
fnRCIAllocator theAllocator() @nogc nothrow @safe @property refGets/sets the allocator for the current thread. This is the default allocator that should be used for allocating thread-local memory. For allocating memory to be shared across threads, use `process...
fnvoid theAllocator(RCIAllocator a) nothrow @system @nogc @propertyDitto
fnRCISharedAllocator processAllocator() @nogc nothrow @trusted @property refGets/sets the allocator for the current process. This allocator must be used for allocating memory shared across threads. Objects created using this allocator can be cast to `shared`.
fnvoid processAllocator(ref RCISharedAllocator a) @nogc nothrow @system @propertyDitto
fnauto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args)Dynamically allocates (using `alloc`) and then creates in the memory allocated an object of type `T`, using `args` (if any) for its initialization. Initialization occurs in the memory allocated and...
private fnvoid fillWithMemcpy(T)(scope void[] array, auto ref T filler) if (T.sizeof == 1) nothrow
private fnvoid fillWithMemcpy(T)(scope void[] array, auto ref T filler) if (T.sizeof != 1) nothrow
private fnT[] uninitializedFillDefault(T)(T[] array) nothrow
fnT[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length)Create an array of `T` with `length` elements using `alloc`. The array is either default-initialized, filled with copies of `init`, or initialized with values fetched from `range`.
fnT[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, T init)Ditto
fnUnqual!(ElementEncodingType!R)[] makeArray(Allocator, R)(auto ref Allocator alloc, R range) if (isInputRange!R && !isInfinite!R)Ditto
fnT[] makeArray(T, Allocator, R)(auto ref Allocator alloc, R range) if (isInputRange!R && !isInfinite!R)Ditto
fnbool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta)Grows `array` by appending `delta` more elements. The needed memory is allocated using `alloc`. The extra elements added are either default- initialized, filled with copies of `init`, or initialize...
fnbool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta, auto ref T init)Ditto
fnbool expandArray(T, Allocator, R)(auto ref Allocator alloc, ref T[] array, R range) if (isInputRange!R)Ditto
fnbool shrinkArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta)Shrinks an array by `delta` elements.
fnvoid dispose(A, T)(auto ref A alloc, auto ref T * p)Destroys and then deallocates (using `alloc`) the object pointed to by a pointer, the class object referred to by a `class` or `interface` reference, or an entire array. It is assumed the respectiv...
fnvoid dispose(A, T)(auto ref A alloc, auto ref T p) if (is(T == class) || is(T == interface))Ditto
fnvoid dispose(A, T)(auto ref A alloc, auto ref T[] array)Ditto
fnauto makeMultidimensionalArray(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] lengths...)Allocates a multidimensional array of elements of type T.
fnvoid disposeMultidimensionalArray(T, Allocator)(auto ref Allocator alloc, auto ref T[] array)Destroys and then deallocates a multidimensional array, assuming it was created with makeMultidimensionalArray and the same allocator was used.
fnRCIAllocator allocatorObject(A)(auto ref A a) if (!isPointer!A)Returns a dynamically-typed `CAllocator` built around a given statically- typed allocator `a` of type `A`. Passing a pointer to the allocator creates a dynamic allocator around the allocator pointe...
fnRCIAllocator allocatorObject(A)(A * pa)Ditto
fnRCISharedAllocator sharedAllocatorObject(A)(auto ref A a) if (!isPointer!A) nothrowReturns a dynamically-typed `CSharedAllocator` built around a given statically- typed allocator `a` of type `A`. Passing a pointer to the allocator creates a dynamic allocator around the allocator ...

Variables 2

private varRCISharedAllocator _processAllocator
private varRCIAllocator _threadAllocator