MmapPool: add expand and empty methods.

This commit is contained in:
Eugen Wissner 2017-01-06 23:12:19 +01:00
parent 254b881da6
commit f3d48234c0
2 changed files with 161 additions and 34 deletions

View File

@ -10,13 +10,15 @@
*/ */
module tanya.memory.allocator; module tanya.memory.allocator;
import std.typecons;
/** /**
* Abstract class implementing a basic allocator. * Abstract class implementing a basic allocator.
*/ */
interface Allocator interface Allocator
{ {
/** /**
* Returns: Alignment. * Returns: Alignment offered.
*/ */
@property uint alignment() const shared pure nothrow @safe @nogc; @property uint alignment() const shared pure nothrow @safe @nogc;
@ -50,6 +52,26 @@ interface Allocator
* Returns: Pointer to the allocated memory. * Returns: Pointer to the allocated memory.
*/ */
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc; bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
/**
* Expands a memory block in place.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool expand(ref void[] p, in size_t size) shared nothrow @nogc;
/**
* Returns $(D Ternary.yes) if no memory is currently allocated from this
* allocator, $(D Ternary.no) if some allocations are currently active, or
* $(D Ternary.unknown) if not supported.
*
* Returns: Whether any memory is currently allocated.
*/
Ternary empty() shared nothrow @nogc;
} }
/** /**

View File

@ -10,9 +10,9 @@
*/ */
module tanya.memory.mmappool; module tanya.memory.mmappool;
import tanya.memory.allocator;
import core.atomic;
import core.stdc.string; import core.stdc.string;
import std.typecons;
import tanya.memory.allocator;
version (Posix) version (Posix)
{ {
@ -72,7 +72,7 @@ final class MmapPool : Allocator
data = initializeRegion(dataSize); data = initializeRegion(dataSize);
} }
return data is null ? null : data[0..size]; return data is null ? null : data[0 .. size];
} }
/// ///
@ -99,7 +99,7 @@ final class MmapPool : Allocator
Block block1; Block block1;
RegionLoop: for (auto r = head; r !is null; r = r.next) RegionLoop: for (auto r = head; r !is null; r = r.next)
{ {
block1 = cast(Block) (cast(void*) r + regionEntrySize); block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof);
do do
{ {
if (block1.free && block1.size >= size) if (block1.free && block1.size >= size)
@ -113,13 +113,13 @@ final class MmapPool : Allocator
{ {
return null; return null;
} }
else if (block1.size >= size + alignment + blockEntrySize) else if (block1.size >= size + alignment_ + BlockEntry.sizeof)
{ // Split the block if needed { // Split the block if needed
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size); Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size);
block2.prev = block1; block2.prev = block1;
block2.next = block1.next; block2.next = block1.next;
block2.free = true; block2.free = true;
block2.size = block1.size - blockEntrySize - size; block2.size = block1.size - BlockEntry.sizeof - size;
block2.region = block1.region; block2.region = block1.region;
if (block1.next !is null) if (block1.next !is null)
@ -132,7 +132,18 @@ final class MmapPool : Allocator
block1.free = false; block1.free = false;
block1.region.blocks = block1.region.blocks + 1; block1.region.blocks = block1.region.blocks + 1;
return cast(void*) block1 + blockEntrySize; return cast(void*) block1 + BlockEntry.sizeof;
}
// Merge block with the next one.
private void mergeNext(Block block) shared const pure nothrow @safe @nogc
{
block.size = block.size + BlockEntry.sizeof + block.next.size;
if (block.next.next !is null)
{
block.next.next.prev = block;
}
block.next = block.next.next;
} }
/** /**
@ -150,7 +161,7 @@ final class MmapPool : Allocator
return true; return true;
} }
Block block = cast(Block) (p.ptr - blockEntrySize); Block block = cast(Block) (p.ptr - BlockEntry.sizeof);
if (block.region.blocks <= 1) if (block.region.blocks <= 1)
{ {
if (block.region.prev !is null) if (block.region.prev !is null)
@ -177,16 +188,11 @@ final class MmapPool : Allocator
// Merge blocks if neigbours are free. // Merge blocks if neigbours are free.
if (block.next !is null && block.next.free) if (block.next !is null && block.next.free)
{ {
block.size = block.size + blockEntrySize + block.next.size; mergeNext(block);
if (block.next.next !is null)
{
block.next.next.prev = block;
}
block.next = block.next.next;
} }
if (block.prev !is null && block.prev.free) if (block.prev !is null && block.prev.free)
{ {
block.prev.size = block.prev.size + blockEntrySize + block.size; block.prev.size = block.prev.size + BlockEntry.sizeof + block.size;
if (block.next !is null) if (block.next !is null)
{ {
block.next.prev = block.prev; block.next.prev = block.prev;
@ -209,6 +215,96 @@ final class MmapPool : Allocator
assert(MmapPool.instance.deallocate(p)); assert(MmapPool.instance.deallocate(p));
} }
/**
* Expands a memory block in place.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool expand(ref void[] p, in size_t size) shared nothrow @nogc
{
if (size <= p.length)
{
return true;
}
if (p is null)
{
return false;
}
Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof);
if (block1.size >= size)
{
// Enough space in the current block. Can happen because of the alignment.
p = p.ptr[0 .. size];
return true;
}
immutable dataSize = addAlignment(size);
immutable delta = dataSize - p.length;
if (block1.next is null || block1.next.size + BlockEntry.sizeof < delta)
{
// It is the last block in the region or the next block is too small.
return false;
}
if (block1.next.size >= delta + alignment_)
{
// We should move the start position of the next block. The order may be
// important because the old block and the new one can overlap.
auto block2 = cast(Block) (p.ptr + dataSize);
block2.free = true;
block2.size = block1.next.size - delta;
block2.region = block1.next.region;
block2.next = block1.next.next;
block2.prev = block1;
block1.size = block1.size + delta;
if (block1.next.next !is null)
{
block1.next.next.prev = block2;
}
block1.next = block2;
}
else
{
// The next block has enough space, but is too small for further
// allocations. Merge it with the current block.
mergeNext(block1);
}
p = p.ptr[0 .. size];
return true;
}
///
nothrow unittest
{
void[] p;
assert(!MmapPool.instance.expand(p, 5));
assert(p is null);
p = MmapPool.instance.allocate(1);
auto orig = p.ptr;
assert(MmapPool.instance.expand(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
assert(MmapPool.instance.expand(p, 4));
assert(p.length == 4);
assert(p.ptr == orig);
assert(MmapPool.instance.expand(p, 2));
assert(p.length == 4);
assert(p.ptr == orig);
MmapPool.instance.deallocate(p);
}
/** /**
* Increases or decreases the size of a memory block. * Increases or decreases the size of a memory block.
* *
@ -296,7 +392,7 @@ final class MmapPool : Allocator
pageSize = sysconf(_SC_PAGE_SIZE); pageSize = sysconf(_SC_PAGE_SIZE);
if (pageSize < 65536) if (pageSize < 65536)
{ {
atomicOp!"*="(pageSize, 65536 / pageSize); pageSize = pageSize * 65536 / pageSize;
} }
} }
else version (Windows) else version (Windows)
@ -326,7 +422,7 @@ final class MmapPool : Allocator
assert(instance is instance); assert(instance is instance);
} }
/** /*
* Initializes a region for one element. * Initializes a region for one element.
* *
* Params: * Params:
@ -379,13 +475,13 @@ final class MmapPool : Allocator
head = region; head = region;
// Initialize the data block // Initialize the data block
void* memoryPointer = p + regionEntrySize; void* memoryPointer = p + RegionEntry.sizeof;
Block block1 = cast(Block) memoryPointer; Block block1 = cast(Block) memoryPointer;
block1.size = size; block1.size = size;
block1.free = false; block1.free = false;
// It is what we want to return // It is what we want to return
void* data = memoryPointer + blockEntrySize; void* data = memoryPointer + BlockEntry.sizeof;
// Free block after data // Free block after data
memoryPointer = data + size; memoryPointer = data + size;
@ -393,26 +489,37 @@ final class MmapPool : Allocator
block1.prev = block2.next = null; block1.prev = block2.next = null;
block1.next = block2; block1.next = block2;
block2.prev = block1; block2.prev = block1;
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2; block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2;
block2.free = true; block2.free = true;
block1.region = block2.region = region; block1.region = block2.region = region;
return data; return data;
} }
/// Ditto.
private void* initializeRegion(size_t size) shared nothrow @nogc private void* initializeRegion(size_t size) shared nothrow @nogc
{ {
return initializeRegion(size, head); return initializeRegion(size, head);
} }
/** /**
* Returns $(D Ternary.yes) if no memory is currently allocated from this
* allocator, $(D Ternary.no) if some allocations are currently active, or
* $(D Ternary.unknown) if not supported.
*
* Returns: Whether any memory is currently allocated.
*/
Ternary empty() shared pure nothrow @safe @nogc
{
// MmapPool always owns some memory because it allocates itself.
return Ternary.no;
}
/*
* Params: * Params:
* x = Space to be aligned. * x = Space to be aligned.
* *
* Returns: Aligned size of $(D_PARAM x). * Returns: Aligned size of $(D_PARAM x).
*/ */
pragma(inline)
private static immutable(size_t) addAlignment(size_t x) private static immutable(size_t) addAlignment(size_t x)
pure nothrow @safe @nogc pure nothrow @safe @nogc
out (result) out (result)
@ -424,13 +531,12 @@ final class MmapPool : Allocator
return (x - 1) / alignment_ * alignment_ + alignment_; return (x - 1) / alignment_ * alignment_ + alignment_;
} }
/** /*
* Params: * Params:
* x = Required space. * x = Required space.
* *
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
*/ */
pragma(inline)
private static immutable(size_t) calculateRegionSize(size_t x) private static immutable(size_t) calculateRegionSize(size_t x)
nothrow @safe @nogc nothrow @safe @nogc
out (result) out (result)
@ -439,18 +545,20 @@ final class MmapPool : Allocator
} }
body body
{ {
x += regionEntrySize + blockEntrySize * 2; x += RegionEntry.sizeof + BlockEntry.sizeof * 2;
return x / pageSize * pageSize + pageSize; return x / pageSize * pageSize + pageSize;
} }
/**
* Returns: Alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc @property uint alignment() shared const pure nothrow @safe @nogc
{ {
return alignment_; return alignment_;
} }
private enum alignment_ = 8; private enum alignment_ = 8;
private static shared MmapPool instance_; private shared static MmapPool instance_;
private shared static size_t pageSize; private shared static size_t pageSize;
private shared struct RegionEntry private shared struct RegionEntry
@ -461,18 +569,15 @@ final class MmapPool : Allocator
size_t size; size_t size;
} }
private alias Region = shared RegionEntry*; private alias Region = shared RegionEntry*;
private enum regionEntrySize = 32;
private shared Region head; private shared Region head;
private shared struct BlockEntry private shared struct BlockEntry
{ {
Block prev; Block prev;
Block next; Block next;
bool free;
size_t size;
Region region; Region region;
size_t size;
bool free;
} }
private alias Block = shared BlockEntry*; private alias Block = shared BlockEntry*;
private enum blockEntrySize = 40;
} }