std.experimental.allocator.building_blocks.bitmapped_block
- struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment, ParentAllocator = NullAllocator, Flag!"multiblock" f = Yes.multiblock);
-
BitmappedBlock
implements a simple heap consisting of one contiguous area of memory organized in blocks, each of sizetheBlockSize
. A block is a unit of allocation. A bitmap serves as bookkeeping data, more precisely one bit per block indicating whether that block is currently allocated or not.Passing
NullAllocator
asParentAllocator
(the default) means user code manages allocation of the memory block from the outside; in that caseBitmappedBlock
must be constructed with aubyte[]
preallocated block and has no responsibility regarding the lifetime of its support underlying storage. If another allocator type is passed,BitmappedBlock
defines a destructor that uses the parent allocator to release the memory block. That makes the combination ofAllocatorList
,BitmappedBlock
, and a back-end allocator such asMmapAllocator
a simple and scalable solution for memory allocation.
There are advantages to storing bookkeeping data separated from the payload (as opposed to e.g. usingAffixAllocator
to store metadata together with each allocation). The layout is more compact (overhead is one bit per block), searching for a free block during allocation enjoys better cache locality, and deallocation does not touch memory around the payload being deallocated (which is often cold).
Allocation requests are handled on a first-fit basis. Although linear in complexity, allocation is in practice fast because of the compact bookkeeping representation, use of simple and fast bitwise routines, and caching of the first available block position. A known issue with this general approach is fragmentation, partially mitigated by coalescing. SinceBitmappedBlock
does not need to maintain the allocated size, freeing memory implicitly coalesces free blocks together. Also, tuningblockSize
has a considerable impact on both internal and external fragmentation.
If the last template parameter is set toNo.multiblock
, the allocator will only serve allocations which require at mosttheBlockSize
. TheBitmappedBlock
has a specialized implementation for single-block allocations which allows for greater performance, at the cost of not being able to allocate more than one block at a time.
The size of each block can be selected either during compilation or at run time. Statically-known block sizes are frequent in practice and yield slightly better performance. To choose a block size statically, pass it as theblockSize
parameter as inBitmappedBlock!(4096)
. To choose a block size parameter, useBitmappedBlock!(chooseAtRuntime)
and pass the block size to the constructor.- Parameters:
theBlockSize the length of a block, which must be a multiple of theAlignment
theAlignment alignment of each block ParentAllocator allocator from which the BitmappedBlock
will draw memory. If set toNullAllocator
, the storage must be passed via the constructorf Yes.multiblock
to support allocations spanning across multiple blocks andNo.multiblock
to support single block allocations. Although limited by single block allocations,No.multiblock
will generally provide higher performance.
- Examples:
-
// Create a block allocator on top of a 10KB stack region. import std.experimental.allocator.building_blocks.region : InSituRegion; import std.traits : hasMember; InSituRegion!(10_240, 64) r; auto a = BitmappedBlock!(64, 64)(cast(ubyte[])(r.allocateAll())); static assert(hasMember!(InSituRegion!(10_240, 64), "allocateAll")); const b = a.allocate(100); writeln(b.length); // 100
- Examples:
-
import std.experimental.allocator.mallocator : Mallocator; import std.typecons : Flag, Yes; enum blockSize = 64; enum numBlocks = 10; // The 'BitmappedBlock' is implicitly instantiated with Yes.multiblock auto a = BitmappedBlock!(blockSize, 8, Mallocator, Yes.multiblock)(numBlocks * blockSize); // Instantiated with Yes.multiblock, can allocate more than one block at a time void[] buf = a.allocate(2 * blockSize); writeln(buf.length); // 2 * blockSize assert(a.deallocate(buf)); // Can also allocate less than one block buf = a.allocate(blockSize / 2); writeln(buf.length); // blockSize / 2 // Expands inside the same block assert(a.expand(buf, blockSize / 2)); writeln(buf.length); // blockSize // If Yes.multiblock, can expand past the size of a single block assert(a.expand(buf, 3 * blockSize)); writeln(buf.length); // 4 * blockSize assert(a.deallocate(buf));
- Examples:
-
import std.experimental.allocator.mallocator : Mallocator; import std.typecons : Flag, No; enum blockSize = 64; auto a = BitmappedBlock!(blockSize, 8, Mallocator, No.multiblock)(1024 * blockSize); // Since instantiated with No.multiblock, can only allocate at most the block size void[] buf = a.allocate(blockSize + 1); assert(buf is null); buf = a.allocate(blockSize); writeln(buf.length); // blockSize assert(a.deallocate(buf)); // This is also fine, because it's less than the block size buf = a.allocate(blockSize / 2); writeln(buf.length); // blockSize / 2 // Can expand the buffer until its length is at most 64 assert(a.expand(buf, blockSize / 2)); writeln(buf.length); // blockSize // Cannot expand anymore assert(!a.expand(buf, 1)); assert(a.deallocate(buf));
- this(ubyte[] data);
this(ubyte[] data, uint blockSize);
this(size_t capacity);
this(ParentAllocator parent, size_t capacity);
this(size_t capacity, uint blockSize);
this(ParentAllocator parent, size_t capacity, uint blockSize); -
Constructs a block allocator given a hunk of memory, or a desired capacity in bytes.
- If
ParentAllocator
isNullAllocator
, only the constructor takingdata
is defined and the user is responsible for freeingdata
if desired. - Otherwise, both constructors are defined. The
data
-based constructor assumes memory has been allocated with the parent allocator. Thecapacity
-based constructor usesParentAllocator
to allocate an appropriate contiguous hunk of memory. Regardless of the constructor used, the destructor releases the memory by usingParentAllocator.deallocate
.
- If
- alias blockSize = theBlockSize;
-
If
blockSize == chooseAtRuntime
,BitmappedBlock
offers a read/write propertyblockSize
. It must be set before any use of the allocator. Otherwise (i.e.theBlockSize
is a legit constant),blockSize
is an alias fortheBlockSize
. Whether constant or variable, must also be a multiple ofalignment
. This constraint isassert
ed statically and dynamically. - alias alignment = theAlignment;
-
The alignment offered is user-configurable statically through parameter
theAlignment
, defaulted toplatformAlignment
. - ParentAllocator parent;
-
The parent allocator. Depending on whether
ParentAllocator
holds state or not, this is a member variable or an alias forParentAllocator.instance
. - pure nothrow @nogc @safe size_t goodAllocSize(size_t n);
-
Returns the actual bytes allocated when
n
bytes are requested, i.e.n.roundUpToMultipleOf(blockSize)
. - const pure nothrow @nogc @trusted Ternary owns(const void[] b);
-
Returns
Ternary.yes
ifb
belongs to theBitmappedBlock
object,Ternary.no
otherwise. Never returnsTernary.unkown
. (This method is somewhat tolerant in that accepts an interior slice.) - pure nothrow @nogc @trusted bool expand(ref void[] b, immutable size_t delta);
-
Expands in place a buffer previously allocated by
BitmappedBlock
. If instantiated withNo.multiblock
, the expansion fails if the new length exceedstheBlockSize
. - nothrow @nogc bool deallocate(void[] b);
-
Deallocates a block previously allocated with this allocator.
- pure nothrow @nogc @trusted void[] allocate(const size_t s);
-
Allocates
s
bytes of memory and returns it, ornull
if memory could not be allocated.The following information might be of help with choosing the appropriate block size. Actual allocation occurs in sizes multiple of the block size. Allocating one block is the fastest because only one 0 bit needs to be found in the metadata. Allocating 2 through 64 blocks is the next cheapest because it affects a maximum of two
ulong
in the metadata. Allocations greater than 64 blocks require a multiword search through the metadata.
If instantiated withNo.multiblock
, it performs a search for the first zero bit in the bitmap and sets it. - @trusted void[] allocateFresh(const size_t s);
-
Allocates s bytes of memory and returns it, or
null
if memory could not be allocated.allocateFresh
behaves just like allocate, the only difference being that this always returns unused(fresh) memory. Although there may still be available space in theBitmappedBlock
,allocateFresh
could still return null, because all the available blocks have been previously deallocated. - void[] allocateAll();
-
If the
BitmappedBlock
object is empty (has no active allocation), allocates all memory within and returns a slice to it. Otherwise, returnsnull
(i.e. no attempt is made to allocate the largest available block). - pure nothrow @nogc @safe Ternary empty();
-
Returns
Ternary.yes
if no memory is currently allocated with this allocator, otherwiseTernary.no
. This method never returnsTernary.unknown
. - pure nothrow @nogc bool deallocateAll();
-
Forcibly deallocates all memory allocated by this allocator, making it available for further allocations. Does not return memory to
ParentAllocator
. - @system bool alignedReallocate(ref void[] b, size_t newSize, uint a);
-
Reallocates a block previously allocated with
alignedAllocate
. Contractions do not occur in place. - @system bool reallocate(ref void[] b, size_t newSize);
-
Reallocates a previously-allocated block. Contractions occur in place.
- void[] alignedAllocate(size_t n, uint a);
-
Allocates a block with specified alignment
a
. The alignment must be a power of 2. Ifa <= alignment
, function forwards toallocate
. Otherwise, it attempts to overallocate and then adjust the result for proper alignment. In the worst case the slack memory is around two blocks.
-
The threadsafe version of the
BitmappedBlock
. The semantics of theSharedBitmappedBlock
are identical to the regularBitmappedBlock
.- Parameters:
theBlockSize the length of a block, which must be a multiple of theAlignment
theAlignment alignment of each block ParentAllocator allocator from which the BitmappedBlock
will draw memory. If set toNullAllocator
, the storage must be passed via the constructorf Yes.multiblock
to support allocations spanning across multiple blocks andNo.multiblock
to support single block allocations. Although limited by single block allocations,No.multiblock
will generally provide higher performance.
- Examples:
-
import std.experimental.allocator.mallocator : Mallocator; import std.experimental.allocator.common : platformAlignment; import std.typecons : Flag, Yes, No; // Create 'numThreads' threads, each allocating in parallel a chunk of memory static void testAlloc(Allocator)(ref Allocator a, size_t allocSize) { import core.thread : ThreadGroup; import std.algorithm.sorting : sort; import core.internal.spinlock : SpinLock; SpinLock lock = SpinLock(SpinLock.Contention.brief); enum numThreads = 10; void[][numThreads] buf; size_t count = 0; // Each threads allocates 'allocSize' void fun() { void[] b = a.allocate(allocSize); writeln(b.length); // allocSize lock.lock(); scope(exit) lock.unlock(); buf[count] = b; count++; } auto tg = new ThreadGroup; foreach (i; 0 .. numThreads) { tg.create(&fun); } tg.joinAll(); // Sorting the allocations made by each thread, we expect the buffers to be // adjacent inside the SharedBitmappedBlock sort!((a, b) => a.ptr < b.ptr)(buf[0 .. numThreads]); foreach (i; 0 .. numThreads - 1) { assert(buf[i].ptr + a.goodAllocSize(buf[i].length) <= buf[i + 1].ptr); } // Deallocate everything foreach (i; 0 .. numThreads) { assert(a.deallocate(buf[i])); } } enum blockSize = 64; auto alloc1 = SharedBitmappedBlock!(blockSize, platformAlignment, Mallocator, Yes.multiblock)(1024 * 1024); auto alloc2 = SharedBitmappedBlock!(blockSize, platformAlignment, Mallocator, No.multiblock)(1024 * 1024); testAlloc(alloc1, 2 * blockSize); testAlloc(alloc2, blockSize);
-
Constructs a block allocator given a hunk of memory, or a desired capacity in bytes.
- If
ParentAllocator
isNullAllocator
, only the constructor takingdata
is defined and the user is responsible for freeingdata
if desired. - Otherwise, both constructors are defined. The
data
-based constructor assumes memory has been allocated with the parent allocator. Thecapacity
-based constructor usesParentAllocator
to allocate an appropriate contiguous hunk of memory. Regardless of the constructor used, the destructor releases the memory by usingParentAllocator.deallocate
.
- If
-
If
blockSize == chooseAtRuntime
,SharedBitmappedBlock
offers a read/write propertyblockSize
. It must be set before any use of the allocator. Otherwise (i.e.theBlockSize
is a legit constant),blockSize
is an alias fortheBlockSize
. Whether constant or variable, must also be a multiple ofalignment
. This constraint isassert
ed statically and dynamically. -
The alignment offered is user-configurable statically through parameter
theAlignment
, defaulted toplatformAlignment
. -
The parent allocator. Depending on whether
ParentAllocator
holds state or not, this is a member variable or an alias forParentAllocator.instance
. -
Returns the actual bytes allocated when
n
bytes are requested, i.e.n.roundUpToMultipleOf(blockSize)
. -
Returns
Ternary.yes
ifb
belongs to theSharedBitmappedBlock
object,Ternary.no
otherwise. Never returnsTernary.unkown
. (This method is somewhat tolerant in that accepts an interior slice.) -
Expands in place a buffer previously allocated by
SharedBitmappedBlock
. Expansion fails if the new length exceeds the block size. -
Deallocates the given buffer
b
, by atomically setting the corresponding bit to0
.b
must be valid, and cannot contain multiple adjacentblocks
. -
Allocates
s
bytes of memory and returns it, ornull
if memory could not be allocated.The
SharedBitmappedBlock
cannot allocate more than the given block size. Allocations are satisfied by searching the first unset bit in the bitmap, and atomically setting it. In rare memory pressure scenarios, the allocation could fail. -
Allocates s bytes of memory and returns it, or
null
if memory could not be allocated.allocateFresh
behaves just like allocate, the only difference being that this always returns unused(fresh) memory. Although there may still be available space in theSharedBitmappedBlock
,allocateFresh
could still return null, because all the available blocks have been previously deallocated. -
If the
SharedBitmappedBlock
object is empty (has no active allocation), allocates all memory within and returns a slice to it. Otherwise, returnsnull
(i.e. no attempt is made to allocate the largest available block). -
Returns
Ternary.yes
if no memory is currently allocated with this allocator, otherwiseTernary.no
. This method never returnsTernary.unknown
. -
Forcibly deallocates all memory allocated by this allocator, making it available for further allocations. Does not return memory to
ParentAllocator
. -
Reallocates a block previously allocated with
alignedAllocate
. Contractions do not occur in place. -
Reallocates a previously-allocated block. Contractions occur in place.
-
Allocates a block with specified alignment
a
. The alignment must be a power of 2. Ifa <= alignment
, function forwards toallocate
. Otherwise, it attempts to overallocate and then adjust the result for proper alignment. In the worst case the slack memory is around two blocks.
- struct BitmappedBlockWithInternalPointers(size_t theBlockSize, uint theAlignment = platformAlignment, ParentAllocator = NullAllocator);
-
A
BitmappedBlock
with additional structure for supportingresolveInternalPointer
. To that end,BitmappedBlockWithInternalPointers
adds a bitmap (one bit per block) that marks object starts. The bitmap itself has variable size and is allocated together with regular allocations.The time complexity of
resolveInternalPointer
is Ο(k
), wherek
is the size of the object within which the internal pointer is looked up.- this(ubyte[] data);
this(size_t capacity);
this(ParentAllocator parent, size_t capacity); -
Constructors accepting desired capacity or a preallocated buffer, similar in semantics to those of
BitmappedBlock
. - alias alignment = theAlignment;
pure nothrow @nogc @safe size_t goodAllocSize(size_t n);
void[] allocate(size_t bytes);
void[] allocateAll();
bool expand(ref void[] b, size_t bytes);
bool deallocate(void[] b);
nothrow @nogc @safe Ternary resolveInternalPointer(const void* p, ref void[] result);
Ternary empty(); -
Allocator primitives.
- this(ubyte[] data);
© 1999–2021 The D Language Foundation
Licensed under the Boost License 1.0.
https://dlang.org/phobos/std_experimental_allocator_building_blocks_bitmapped_block.html