phobos/std/experimental/allocator/building_blocks/package.d
Vladimir Panteleev 86cf380007 HTML fixes
2015-10-24 06:19:23 +00:00

313 lines
16 KiB
D

/**
$(H2 Assembling Your Own Allocator)
In addition to defining the interfaces above, this package also implements
untyped composable memory allocators. They are $(I untyped) because they deal
exclusively in $(D void[]) and have no notion of what type the memory allocated
would be destined for. They are $(I composable) because the included allocators
are building blocks that can be assembled in complex nontrivial allocators.
$(P Unlike the allocators for the C and C++ programming languages, which manage
the allocated size internally, these allocators require that the client
maintains (or knows $(I a priori)) the allocation size for each piece of memory
allocated. Put simply, the client must pass the allocated size upon
deallocation. Storing the size in the _allocator has significant negative
performance implications, and is virtually always redundant because client code
needs knowledge of the allocated size in order to avoid buffer overruns. (See
more discussion in a $(WEB open-
std.org/JTC1/SC22/WG21/docs/papers/2013/n3536.html, proposal) for sized
deallocation in C++.) For this reason, allocators herein traffic in $(D void[])
as opposed to $(D void*).)
$(P In order to be usable as an _allocator, a type should implement the
following methods with their respective semantics. Only $(D alignment) and $(D
allocate) are required. If any of the other methods is missing, the _allocator
is assumed to not have that capability (for example some allocators do not offer
manual deallocation of memory). Allocators should NOT implement
unsupported methods to always fail. For example, an allocator that lacks the
capability to implement `alignedAllocate` should not define it at all (as
opposed to defining it to always return `null` or throw an exception). The
missing implementation statically informs other components about the
allocator's capabilities and allows them to make design decisions accordingly.)
$(BOOKTABLE ,
$(TR $(TH Method name) $(TH Semantics))
$(TR $(TDC uint alignment;, $(POST $(RES) > 0)) $(TD Returns the minimum
alignment of all data returned by the allocator. An allocator may implement $(D
alignment) as a statically-known $(D enum) value only. Applications that need
dynamically-chosen alignment values should use the $(D alignedAllocate) and $(D
alignedReallocate) APIs.))
$(TR $(TDC size_t goodAllocSize(size_t n);, $(POST $(RES) >= n)) $(TD Allocators
customarily allocate memory in discretely-sized chunks. Therefore, a request for
$(D n) bytes may result in a larger allocation. The extra memory allocated goes
unused and adds to the so-called $(WEB goo.gl/YoKffF,internal fragmentation).
The function $(D goodAllocSize(n)) returns the actual number of bytes that would
be allocated upon a request for $(D n) bytes. This module defines a default
implementation that returns $(D n) rounded up to a multiple of the allocator's
alignment.))
$(TR $(TDC void[] allocate(size_t s);, $(POST $(RES) is null || $(RES).length ==
s)) $(TD If $(D s == 0), the call may return any empty slice (including $(D
null)). Otherwise, the call allocates $(D s) bytes of memory and returns the
allocated block, or $(D null) if the request could not be satisfied.))
$(TR $(TDC void[] alignedAllocate(size_t s, uint a);, $(POST $(RES) is null ||
$(RES).length == s)) $(TD Similar to `allocate`, with the additional
guarantee that the memory returned is aligned to at least `a` bytes. `a`
must be a power of 2.))
$(TR $(TDC void[] allocateAll();) $(TD Offers all of allocator's memory to the
caller, so it's usually defined by fixed-size allocators. If the allocator is
currently NOT managing any memory, then $(D allocateAll()) shall allocate and
return all memory available to the allocator, and subsequent calls to all
allocation primitives should not succeed (e..g $(D allocate) shall return $(D
null) etc). Otherwise, $(D allocateAll) only works on a best-effort basis, and
the allocator is allowed to return $(D null) even if does have available memory.
Memory allocated with $(D allocateAll) is not otherwise special (e.g. can be
reallocated or deallocated with the usual primitives, if defined).))
$(TR $(TDC bool expand(ref void[] b, size_t delta);, $(POST !$(RES) || b.length
== $(I old)(b).length + delta)) $(TD Expands $(D b) by $(D delta) bytes. If $(D
delta == 0), succeeds without changing $(D b). If $(D b is null), the call
evaluates $(D b = allocate(delta)) and returns $(D b !is null). Otherwise, $(D
b) must be a buffer previously allocated with the same allocator. If expansion
was successful, $(D expand) changes $(D b)'s length to $(D b.length + delta) and
returns $(D true). Upon failure, the call effects no change upon the allocator
object, leaves $(D b) unchanged, and returns $(D false).))
$(TR $(TDC bool reallocate(ref void[] b, size_t s);, $(POST !$(RES) || b.length
== s)) $(TD Reallocates $(D b) to size $(D s), possibly moving memory around.
$(D b) must be $(D null) or a buffer allocated with the same allocator. If
reallocation was successful, $(D reallocate) changes $(D b) appropriately and
returns $(D true). Upon failure, the call effects no change upon the allocator
object, leaves $(D b) unchanged, and returns $(D false). An allocator should
implement $(D reallocate) if it can derive some advantage from doing so;
otherwise, this module defines a $(D reallocate) free function implemented in
terms of $(D expand), $(D allocate), and $(D deallocate).))
$(TR $(TDC bool alignedReallocate(ref void[] b,$(BR) size_t s, uint a);, $(POST
!$(RES) || b.length == s)) $(TD Similar to $(D reallocate), but guarantees the
reallocated memory is aligned at $(D a) bytes. The buffer must have been
originated with a call to $(D alignedAllocate). $(D a) must be a power of 2
greater than $(D (void*).sizeof). An allocator should implement $(D
alignedReallocate) if it can derive some advantage from doing so; otherwise,
this module defines a $(D alignedReallocate) free function implemented in terms
of $(D expand), $(D alignedAllocate), and $(D deallocate).))
$(TR $(TDC Ternary owns(void[] b);) $(TD Returns `Ternary.yes` if `b` has been
allocated with this allocator. An allocator should define this method only if it
can decide on ownership precisely and fast (in constant time, logarithmic time,
or linear time with a low multiplication factor). Traditional allocators such as
the C heap do not define such functionality. If $(D b is null), the allocator
shall return `Ternary.no`, i.e. no allocator owns the `null` slice.))
$(TR $(TDC void[] resolveInternalPointer(void* p);) $(TD If $(D p) is a pointer
somewhere inside a block allocated with this allocator, returns a pointer to the
beginning of the allocated block. Otherwise, returns $(D null). If the pointer
points immediately after an allocated block, the result is implementation
defined.))
$(TR $(TDC bool deallocate(void[] b);) $(TD If $(D b is null), does
nothing and returns `true`. Otherwise, deallocates memory previously allocated
with this allocator and returns `true` if successful, `false` otherwise. An
implementation that would not support deallocation (i.e. would always return
`false` should not define this primitive at all.)))
$(TR $(TDC bool deallocateAll();, $(POST empty)) $(TD Deallocates all memory
allocated with this allocator. If an allocator implements this method, it must
specify whether its destructor calls it, too.))
$(TR $(TDC Ternary empty();) $(TD Returns `Ternary.yes` if and only if the
allocator holds no memory (i.e. no allocation has occurred, or all allocations
have been deallocated).))
$(TR $(TDC static Allocator instance;, $(POST instance $(I is a valid)
Allocator $(I object))) $(TD Some allocators are $(I monostate), i.e. have only
an instance and hold only global state. (Notable examples are C's own
`malloc`-based allocator and D's garbage-collected heap.) Such allocators must
define a static $(D instance) instance that serves as the symbolic placeholder
for the global instance of the allocator. An allocator should not hold state
and define `instance` simultaneously. Depending on whether the allocator is
thread-safe or not, this instance may be $(D shared).))
)
$(H2 Sample Assembly)
The example below features an _allocator modeled after $(WEB goo.gl/m7329l,
jemalloc), which uses a battery of free-list allocators spaced so as to keep
internal fragmentation to a minimum. The $(D FList) definitions specify no
bounds for the freelist because the $(D Segregator) does all size selection in
advance.
Sizes through 3584 bytes are handled via freelists of staggered sizes. Sizes
from 3585 bytes through 4072 KB are handled by a $(D BitmappedBlock) with a
block size of 4 KB. Sizes above that are passed direct to the $(D Mallocator).
----
alias FList = FreeList!(GCAllocator, 0, unbounded);
alias A = Segregator!(
8, FreeList!(GCAllocator, 0, 8),
128, Bucketizer!(FList, 1, 128, 16),
256, Bucketizer!(FList, 129, 256, 32),
512, Bucketizer!(FList, 257, 512, 64),
1024, Bucketizer!(FList, 513, 1024, 128),
2048, Bucketizer!(FList, 1025, 2048, 256),
3584, Bucketizer!(FList, 2049, 3584, 512),
4072 * 1024, AllocatorList!(
() => BitmappedBlock!(GCAllocator, 4096)(4072 * 1024)),
GCAllocator
);
A tuMalloc;
auto b = tuMalloc.allocate(500);
assert(b.length == 500);
auto c = tuMalloc.allocate(113);
assert(c.length == 113);
assert(tuMalloc.expand(c, 14));
tuMalloc.deallocate(b);
tuMalloc.deallocate(c);
----
$(H2 Allocating memory for sharing across threads)
One allocation pattern used in multithreaded applications is to share memory
across threads, and to deallocate blocks in a different thread than the one that
allocated it.
All allocators in this module accept and return $(D void[]) (as opposed to
$(D shared void[])). This is because at the time of allocation, deallocation, or
reallocation, the memory is effectively not $(D shared) (if it were, it would
reveal a bug at the application level).
The issue remains of calling $(D a.deallocate(b)) from a different thread than
the one that allocated $(D b). It follows that both threads must have access to
the same instance $(D a) of the respective allocator type. By definition of D,
this is possible only if $(D a) has the $(D shared) qualifier. It follows that
the allocator type must implement $(D allocate) and $(D deallocate) as $(D
shared) methods. That way, the allocator commits to allowing usable $(D shared)
instances.
Conversely, allocating memory with one non-$(D shared) allocator, passing it
across threads (by casting the obtained buffer to $(D shared)), and later
deallocating it in a different thread (either with a different allocator object
or with the same allocator object after casting it to $(D shared)) is illegal.
$(H2 Building Blocks)
$(P The table below gives a synopsis of predefined allocator building blocks,
with their respective modules. Either `import` the needed modules individually,
or `import` `std.experimental.building_blocks`, which imports them all
`public`ly. The building blocks can be assembled in unbounded ways and also
combined with your own. For a collection of typical and useful preassembled
allocators and for inspiration in defining more such assemblies, refer to
$(LINK2 std_experimental_allocator_showcase.html,
`std.experimental.allocator.building_blocks.showcase`).)
$(BOOKTABLE,
$(TR $(TH Allocator$(BR)) $(TH Description))
$(TR $(TDC2 NullAllocator, null_allocator) $(TD Very good at doing absolutely nothing. A good
starting point for defining other allocators or for studying the API.))
$(TR $(TDC3 GCAllocator, gc_allocator) $(TD The system-provided garbage-collector allocator.
This should be the default fallback allocator tapping into system memory. It
offers manual $(D free) and dutifully collects litter.))
$(TR $(TDC3 Mallocator, mallocator) $(TD The C heap _allocator, a.k.a. $(D
malloc)/$(D realloc)/$(D free). Use sparingly and only for code that is unlikely
to leak.))
$(TR $(TDC3 AlignedMallocator, mallocator) $(TD Interface to OS-specific _allocators that
support specifying alignment:
$(WEB man7.org/linux/man-pages/man3/posix_memalign.3.html, $(D posix_memalign))
on Posix and $(WEB msdn.microsoft.com/en-us/library/fs9stz4e(v=vs.80).aspx,
$(D __aligned_xxx)) on Windows.))
$(TR $(TDC2 AffixAllocator, affix_allocator) $(TD Allocator that allows and manages allocating
extra prefix and/or a suffix bytes for each block allocated.))
$(TR $(TDC2 BitmappedBlock, bitmapped_block) $(TD Organizes one contiguous chunk of memory in
equal-size blocks and tracks allocation status at the cost of one bit per
block.))
$(TR $(TDC2 FallbackAllocator, fallback_allocator) $(TD Allocator that combines two other allocators
- primary and fallback. Allocation requests are first tried with primary, and
upon failure are passed to the fallback. Useful for small and fast allocators
fronting general-purpose ones.))
$(TR $(TDC2 FreeList, free_list) $(TD Allocator that implements a $(WEB
wikipedia.org/wiki/Free_list, free list) on top of any other allocator. The
preferred size, tolerance, and maximum elements are configurable at compile- and
run time.))
$(TR $(TDC2 SharedFreeList, free_list) $(TD Same features as $(D FreeList), but packaged as
a $(D shared) structure that is accessible to several threads.))
$(TR $(TDC2 FreeTree, free_tree) $(TD Allocator similar to $(D FreeList) that uses a
binary search tree to adaptively store not one, but many free lists.))
$(TR $(TDC2 Region, region) $(TD Region allocator organizes a chunk of memory as a
simple bump-the-pointer allocator.))
$(TR $(TDC2 InSituRegion, region) $(TD Region holding its own allocation, most often on
the stack. Has statically-determined size.))
$(TR $(TDC2 SbrkRegion, region) $(TD Region using $(D $(LUCKY sbrk)) for allocating
memory.))
$(TR $(TDC3 MmapAllocator, mmap_allocator) $(TD Allocator using $(D $(LUCKY mmap)) directly.))
$(TR $(TDC2 StatsCollector, stats_collector) $(TD Collect statistics about any other
allocator.))
$(TR $(TDC2 Quantizer, quantizer) $(TD Allocates in coarse-grained quantas, thus
improving performance of reallocations by often reallocating in place. The drawback is higher memory consumption because of allocated and unused memory.))
$(TR $(TDC2 AllocatorList, allocator_list) $(TD Given an allocator factory, lazily creates as
many allocators as needed to satisfy allocation requests. The allocators are
stored in a linked list. Requests for allocation are satisfied by searching the
list in a linear manner.))
$(TR $(TDC2 Segregator, segregator) $(TD Segregates allocation requests by size
and dispatches them to distinct allocators.))
$(TR $(TDC2 Bucketizer, bucketizer) $(TD Divides allocation sizes in discrete buckets and
uses an array of allocators, one per bucket, to satisfy requests.))
$(COMMENT $(TR $(TDC2 InternalPointersTree) $(TD Adds support for resolving internal
pointers on top of another allocator.)))
)
Macros:
MYREF = $(LINK2 std_experimental_allocator_building_blocks_$2.html, $1) 
MYREF2 = $(LINK2 std_experimental_allocator_building_blocks_$2.html#$1, $1) 
MYREF3 = $(LINK2 std_experimental_allocator_$2.html#$1, $1) 
TDC = $(TDNW $(D $1)$+)
TDC2 = $(TDNW $(D $(MYREF2 $1,$+))$(BR)$(SMALL
$(D std.experimental.allocator.building_blocks.$2)))
TDC3 = $(TDNW $(D $(MYREF3 $1,$+))$(BR)$(SMALL
$(D std.experimental.allocator.$2)))
RES = $(I result)
POST = $(BR)$(SMALL $(I Post:) $(BLUE $(D $0)))
*/
module std.experimental.allocator.building_blocks;
public import
std.experimental.allocator.building_blocks.affix_allocator,
std.experimental.allocator.building_blocks.allocator_list,
std.experimental.allocator.building_blocks.bucketizer,
std.experimental.allocator.building_blocks.fallback_allocator,
std.experimental.allocator.building_blocks.free_list,
std.experimental.allocator.building_blocks.free_tree,
std.experimental.allocator.gc_allocator,
std.experimental.allocator.building_blocks.bitmapped_block,
std.experimental.allocator.building_blocks.kernighan_ritchie,
std.experimental.allocator.mallocator,
std.experimental.allocator.mmap_allocator,
std.experimental.allocator.building_blocks.null_allocator,
std.experimental.allocator.building_blocks.quantizer,
std.experimental.allocator.building_blocks.region,
std.experimental.allocator.building_blocks.segregator,
std.experimental.allocator.building_blocks.stats_collector;