Add alignedAllocate/Reallocate, improve documentation

This commit is contained in:
Andrei Alexandrescu 2015-05-14 00:29:57 -07:00
parent 0d6ae0459d
commit 2e435bd4d0

View file

@ -40,8 +40,6 @@ blockSize) parameter as in $(D HeapBlock!(Allocator, 4096)). To choose a block
size parameter, use $(D HeapBlock!(Allocator, chooseAtRuntime)) and pass the size parameter, use $(D HeapBlock!(Allocator, chooseAtRuntime)) and pass the
block size to the constructor. block size to the constructor.
TODO: implement $(D alignedAllocate) and $(D alignedReallocate).
*/ */
struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment, struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
ParentAllocator = NullAllocator) ParentAllocator = NullAllocator)
@ -66,9 +64,11 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
/** /**
If $(D blockSize == chooseAtRuntime), $(D HeapBlock) offers a read/write If $(D blockSize == chooseAtRuntime), $(D HeapBlock) offers a read/write
property $(D blockSize). It must be set to a power of two before any use property $(D blockSize). It must be set before any use of the allocator.
of the allocator. Otherwise, $(D blockSize) is an alias for $(D Otherwise (i.e. $(D theBlockSize) is a legit constant), $(D blockSize) is
theBlockSize). an alias for $(D theBlockSize). Whether constant or variable, must also be
a multiple of $(D alignment). This constraint is $(D assert)ed statically
and dynamically.
*/ */
static if (theBlockSize != chooseAtRuntime) static if (theBlockSize != chooseAtRuntime)
{ {
@ -96,14 +96,14 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
} }
/** /**
The alignment offered is user-configurable statically through parameter The _alignment offered is user-configurable statically through parameter
$(D theAlignment), defaulted to $(D platformAlignment). $(D theAlignment), defaulted to $(D platformAlignment).
*/ */
alias alignment = theAlignment; alias alignment = theAlignment;
// state { // state {
/** /**
The parent allocator. Depending on whether $(D ParentAllocator) holds state The _parent allocator. Depending on whether $(D ParentAllocator) holds state
or not, this is a member variable or an alias for $(D ParentAllocator.it). or not, this is a member variable or an alias for $(D ParentAllocator.it).
*/ */
static if (stateSize!ParentAllocator) static if (stateSize!ParentAllocator)
@ -135,20 +135,20 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
} }
/** /**
Constructs a block allocator given desired capacity in bytes. Constructs a block allocator given a hunk of memory, or a desired capacity
*/ in bytes.
static if (!is(ParentAllocator == NullAllocator))
this(size_t capacity)
{
size_t toAllocate = totalAllocation(capacity);
auto data = parent.allocate(toAllocate);
this(data);
assert(_blocks * blockSize >= capacity);
}
/** $(UL
Constructs a block allocator given a hunk of memory. The layout puts the $(LI If $(D ParentAllocator) is $(D NullAllocator), only the constructor
bitmap at the front followed immediately by the payload. taking $(D data) is defined and the user is responsible for freeing $(D
data) if desired.)
$(LI Otherwise, both constructors are defined. The $(D data)-based
constructor assumes memory has been allocated with the parent allocator.
The $(D capacity)-based constructor uses $(D ParentAllocator) to allocate
an appropriate contiguous hunk of memory. Regardless of the constructor
used, the destructor releases the memory by using $(D
ParentAllocator.deallocate).)
)
*/ */
this(void[] data) this(void[] data)
{ {
@ -181,6 +181,16 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
} }
} }
/// Ditto
static if (!is(ParentAllocator == NullAllocator))
this(size_t capacity)
{
size_t toAllocate = totalAllocation(capacity);
auto data = parent.allocate(toAllocate);
this(data);
assert(_blocks * blockSize >= capacity);
}
/** /**
If $(D ParentAllocator) is not $(D NullAllocator) and defines $(D If $(D ParentAllocator) is not $(D NullAllocator) and defines $(D
deallocate), the destructor is defined to deallocate the block held. deallocate), the destructor is defined to deallocate the block held.
@ -224,12 +234,16 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
} }
/** /**
Standard allocator methods per the semantics defined above. The $(D Allocates $(D s) bytes of memory and returns it, or $(D null) if memory
deallocate) and $(D reallocate) methods are $(D @system) because they may could not be allocated.
move memory around, leaving dangling pointers in user code.
BUGS: Neither $(D deallocateAll) nor the destructor free the original memory The following information might be of help with choosing the appropriate
block. Either user code or the parent allocator should carry that. 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 $(D ulong)s in the metadata.
Allocations greater than 64 blocks require a multiword search through the
metadata.
*/ */
@trusted void[] allocate(const size_t s) @trusted void[] allocate(const size_t s)
{ {
@ -261,7 +275,7 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
goto case 0; // fall through goto case 0; // fall through
case 0: case 0:
return null; return null;
case 2: .. case 63: case 2: .. case 64:
result = smallAlloc(cast(uint) blocks); result = smallAlloc(cast(uint) blocks);
break; break;
default: default:
@ -271,7 +285,47 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
return result.ptr ? result.ptr[0 .. s] : null; return result.ptr ? result.ptr[0 .. s] : null;
} }
/// Ditto /**
Allocates a block with specified alignment $(D a). The alignment must be a
power of 2. If $(D a <= alignment), function forwards to $(D allocate).
Otherwise, it attempts to overallocate and then adjust the result for
proper alignment. In the worst case the slack memory is around two blocks.
*/
void[] alignedAllocate(size_t n, uint a)
{
assert(a.isPowerOf2);
if (a <= alignment) return allocate(n);
// Overallocate to make sure we can get an aligned block
auto b = allocate((n + a - alignment).roundUpToMultipleOf(blockSize));
if (!b.ptr) return null;
auto result = b.roundStartToMultipleOf(a);
assert(result.length >= n);
result = result.ptr[0 .. n]; // final result
// Free any blocks that might be slack at the beginning
auto slackHeadingBlocks = (result.ptr - b.ptr) / blockSize;
if (slackHeadingBlocks)
{
deallocate(b[0 .. slackHeadingBlocks * blockSize]);
}
// Free any blocks that might be slack at the end
auto slackTrailingBlocks = ((b.ptr + b.length)
- (result.ptr + result.length)) / blockSize;
if (slackTrailingBlocks)
{
deallocate(b[$ - slackTrailingBlocks * blockSize .. $]);
}
return result;
}
/**
If the $(D HeapBlock) object is empty (has no active allocation), allocates
all memory within and returns a slice to it. Otherwise, returns $(D null)
(i.e. no attempt is made to allocate the largest available block).
*/
void[] allocateAll() void[] allocateAll()
{ {
if (!empty) return null; if (!empty) return null;
@ -279,7 +333,10 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
return _payload; return _payload;
} }
/// Ditto /**
Returns $(D true) if $(D b) belongs to the $(D HeapBlock) object. This
method is somewhat tolerant in that accepts an interior slice.
*/
bool owns(void[] b) const bool owns(void[] b) const
{ {
assert(b.ptr !is null || b.length == 0, "Corrupt block."); assert(b.ptr !is null || b.length == 0, "Corrupt block.");
@ -433,9 +490,12 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
return true; return true;
} }
/// Ditto /**
Expands an allocated block in place.
*/
@trusted bool expand(ref void[] b, immutable size_t delta) @trusted bool expand(ref void[] b, immutable size_t delta)
{ {
// Dispose with trivial corner cases
if (delta == 0) return true; if (delta == 0) return true;
if (b is null) if (b is null)
{ {
@ -443,6 +503,10 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
return b !is null; return b !is null;
} }
/* To simplify matters, refuse to expand buffers that don't start at a block start (this may be the case for blocks allocated with alignedAllocate).
*/
if ((b.ptr - _payload.ptr) % blockSize) return false;
const blocksOld = bytes2blocks(b.length); const blocksOld = bytes2blocks(b.length);
const blocksNew = bytes2blocks(b.length + delta); const blocksNew = bytes2blocks(b.length + delta);
assert(blocksOld <= blocksNew); assert(blocksOld <= blocksNew);
@ -475,7 +539,9 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
return true; return true;
} }
/// Ditto /**
Reallocates a previously-allocated block. Contractions occur in place.
*/
@system bool reallocate(ref void[] b, size_t newSize) @system bool reallocate(ref void[] b, size_t newSize)
{ {
if (newSize == 0) if (newSize == 0)
@ -492,23 +558,46 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
b = b[0 .. newSize]; b = b[0 .. newSize];
return true; return true;
} }
// Attempt an in-place expansion first
const delta = newSize - b.length;
if (expand(b, delta)) return true;
// Go the slow route // Go the slow route
return .reallocate(this, b, newSize); return .reallocate(this, b, newSize);
} }
/// Ditto /**
Reallocates a block previously allocated with $(D alignedAllocate). Contractions do not occur in place.
*/
@system bool alignedReallocate(ref void[] b, size_t newSize, uint a)
{
if (newSize == 0)
{
deallocate(b);
b = null;
return true;
}
// Go the slow route
return .alignedReallocate(this, b, newSize, a);
}
/**
Deallocates a block previously allocated with this allocator.
*/
void deallocate(void[] b) void deallocate(void[] b)
{ {
if (b is null) return; if (b is null) return;
// Round up size to multiple of block size // Adjust pointer, might be inside a block after alignedAllocate
auto blocks = b.length.divideRoundUp(blockSize); //auto p = (b.ptr - _payload.ptr) / blockSize
// Locate position // Locate position
auto pos = b.ptr - _payload.ptr; auto pos = b.ptr - _payload.ptr;
assert(pos % blockSize == 0);
auto blockIdx = pos / blockSize; auto blockIdx = pos / blockSize;
// Adjust pointer, might be inside a block due to alignedAllocate
auto begin = _payload.ptr + blockIdx * blockSize,
end = b.ptr + b.length;
b = begin[0 .. end - begin];
// Round up size to multiple of block size
auto blocks = b.length.divideRoundUp(blockSize);
// Get into details
auto wordIdx = blockIdx / 64, msbIdx = cast(uint) (blockIdx % 64); auto wordIdx = blockIdx / 64, msbIdx = cast(uint) (blockIdx % 64);
if (_startIdx > wordIdx) _startIdx = wordIdx; if (_startIdx > wordIdx) _startIdx = wordIdx;
@ -545,14 +634,20 @@ struct HeapBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
} }
} }
/// Ditto /**
Forcibly deallocates all memory allocated by this allocator, making it
available for further allocations. Does not return memory to $(D
ParentAllocator).
*/
void deallocateAll() void deallocateAll()
{ {
_control[] = 0; _control[] = 0;
_startIdx = 0; _startIdx = 0;
} }
/// Ditto /**
Returns $(D true) iff no memory is currently allocated with this allocator.
*/
bool empty() bool empty()
{ {
return _control.allAre0(); return _control.allAre0();