stdx.allocator.building_blocks.kernighan_ritchie
-
Declaration
struct
KRRegion
(ParentAllocator = NullAllocator);draws inspiration from the and also the described by Brian Kernighan and Dennis Ritchie in section 8.7 of the book , Second Edition, Prentice Hall, 1988.
Discussion
Initially,
starts in "region" mode: allocations are served from the memory chunk in a region fashion. Thus, as long as there is enough memory left, has the performance profile of a region allocator. Deallocation inserts (in time) the deallocated blocks in an unstructured freelist, which is not read in region mode.KRRegion
Once the region cannot serve an request, switches to "free list" mode. It sorts the list of previously deallocated blocks by address and serves allocation requests off that free list. The allocation and deallocation follow the pattern described by Kernighan and Ritchie.
The recommended use of
is as a region with deallocation. If theKRRegion
is dimensioned appropriately, it could often not enter free list mode during its lifetime. Thus it is as fast as a simple region, whilst offering deallocation at a small cost. When the region memory is exhausted, the previously deallocated memory is still usable, at a performance cost. If the region is not excessively large and fragmented, the linear allocation and deallocation cost may still be compensated for by the good locality characteristics.KRRegion
If the chunk of memory managed is large, it may be desirable to switch management to free list from the beginning. That way, memory may be used in a more compact manner than region mode. To force free list mode, call shortly after construction or when deemed appropriate.
The smallest size that can be allocated is two words (16 bytes on 64-bit systems, 8 bytes on 32-bit systems). This is because the free list management needs two words (one for the length, the other for the next pointer in the singly-linked list).
The type parameter is the type of the allocator used to allocate the memory chunk underlying the object. Choosing the default () means the user is responsible for passing a buffer at construction (and for deallocating it if necessary). Otherwise, automatically deallocates the buffer during destruction. For that reason, if is not , then is not copyable.
In free list mode, embeds a free blocks list onto the chunk of memory. The free list is circular, coalesced, and sorted by address at all times. Allocations and deallocations take time proportional to the number of previously deallocated blocks. (In practice the cost may be lower, e.g. if memory is deallocated in reverse order of allocation, all operations take constant time.) Memory utilization is good (small control structure and no per-allocation overhead). The disadvantages of freelist mode include proneness to fragmentation, a minimum allocation size of two words, and linear worst-case allocation and deallocation times.
Similarities of
(in free list mode) with the Kernighan-Ritchie allocator:KRRegion
- Free blocks have variable size and are linked in a singly-linked list.
- The freelist is maintained in increasing address order, which makes coalescing easy.
- The strategy for finding the next available block is first fit.
- The free list is circular, with the last node pointing back to the first.
- Coalescing is carried during deallocation.
Differences from the Kernighan-Ritchie allocator:
- Once the chunk is exhausted, the Kernighan-Ritchie allocator allocates another chunk using operating system primitives. For better composability, just gets full (returns on new allocation requests). The decision to allocate more blocks is deferred to a higher-level entity. For an example, see the example below using in conjunction with .
- Allocated blocks do not hold a size prefix. This is because in D the size information is available in client code at deallocation time.
Examples
is preferable to as a front for a general-purpose allocator if is needed, yet the actual deallocation traffic is relatively low. The example below shows a using stack storage fronting the GC allocator.
import stdx.allocator.building_blocks.fallback_allocator : fallbackAllocator; import stdx.allocator.gc_allocator : GCAllocator; import stdx.allocator.internal : Ternary; // KRRegion fronting a general-purpose allocator ubyte[1024 * 128] buf; auto alloc = fallbackAllocator(KRRegion!()(buf), GCAllocator.instance); auto b = alloc.allocate(100); assert(b.length == 100); assert(alloc.primary.owns(b) == Ternary.yes);
Examples
The code below defines a scalable allocator consisting of 1 MB (or larger) blocks fetched from the garbage-collected heap. Each block is organized as a KR-style heap. More blocks are allocated and freed on a need basis.
This is the closest example to the allocator introduced in the KR book. It should perform slightly better because instead of searching through one large free list, it searches through several shorter lists in LRU order. Also, it actually returns memory to the operating system when possible.import std.algorithm.comparison : max; import stdx.allocator.building_blocks.allocator_list : AllocatorList; import stdx.allocator.gc_allocator : GCAllocator; import stdx.allocator.mmap_allocator : MmapAllocator; AllocatorList!(n => KRRegion!MmapAllocator(max(n * 16, 1024 * 1024))) alloc;
-
Declaration
ParentAllocator
parent
;If holds state, is a public member of type . Otherwise, is an for
ParentAllocator.instance
. -
Declaration
this(ubyte[]
b
);
this(size_tn
);Create a . If is not , 's destructor will call .
Parameters
ubyte[]
b
Block of memory to serve as support for the allocator. Memory must be larger than two words and word-aligned.
size_t
n
Capacity desired. This constructor is defined only if is not .
-
Declaration
void
switchToFreeList
();Forces free list mode. If already in free list mode, does nothing. Otherwise, sorts the free list accumulated so far and switches strategy for future allocations to KR style.
-
Declaration
enum auto
alignment
;Word-level
alignment
. -
Declaration
void[]
allocate
(size_tn
);Allocates bytes. Allocation searches the list of available blocks until a free block with or more bytes is found (first fit strategy). The block is split (if larger) and returned.
Parameters
size_t
n
number of bytes to allocate
Return Value
A word-aligned buffer of bytes, or .
-
Declaration
bool
deallocate
(void[]b
);Deallocates , which is assumed to have been previously allocated with this allocator. Deallocation performs a linear search in the free list to preserve its sorting order. It follows that blocks with higher addresses in allocators with many free blocks are slower to
deallocate
.Parameters
void[]
b
block to be deallocated
-
Declaration
void[]
allocateAll
();Allocates all memory available to this allocator. If the allocator is empty, returns the entire available block of memory. Otherwise, it still performs a best-effort allocation: if there is no fragmentation (e.g. has been used but not ), allocates and returns the only available block of memory.
Discussion
The operation takes time proportional to the number of adjacent free blocks at the front of the free list. These blocks get coalesced, whether succeeds or fails due to fragmentation.
Examples
import stdx.allocator.gc_allocator : GCAllocator; auto alloc = KRRegion!GCAllocator(1024 * 64); const b1 = alloc.allocate(2048); assert(b1.length == 2048); const b2 = alloc.allocateAll; assert(b2.length == 1024 * 62);
-
Declaration
bool
deallocateAll
();Deallocates all memory currently allocated, making the allocator ready for other allocations. This is a operation.
-
Declaration
Ternary
owns
(void[]b
);Checks whether the allocator is responsible for the allocation of . It does a simple range check. should be a buffer either allocated with or obtained through other means.
-
Declaration
static size_t
goodAllocSize
(size_tn
);Adjusts to a size suitable for allocation (two words or larger, word-aligned).
-
Declaration
Ternary
empty
();Return Value
Ternary.yes
if the allocator isempty
,Ternary.no
otherwise. Never returnsTernary.unknown
.