Added AscendingPageAllocator

remove trailing whitespace
This commit is contained in:
Alexandru Jercaianu 2017-11-21 00:42:35 +02:00
parent 711291c922
commit d7479b7964
4 changed files with 431 additions and 1 deletions

View file

@ -200,7 +200,7 @@ PACKAGE_std_experimental_logger = core filelogger \
PACKAGE_std_experimental_allocator = \
common gc_allocator mallocator mmap_allocator package showcase typed
PACKAGE_std_experimental_allocator_building_blocks = \
affix_allocator allocator_list bucketizer \
affix_allocator allocator_list ascending_page_allocator bucketizer \
fallback_allocator free_list free_tree bitmapped_block \
kernighan_ritchie null_allocator package quantizer \
region scoped_allocator segregator stats_collector

View file

@ -0,0 +1,418 @@
module std.experimental.allocator.building_blocks.ascending_page_allocator;
import std.experimental.allocator.common;
/**
`AscendingPageAllocator` is a fast and safe allocator which rounds all allocations
to page size multiples. It reserves a range of virtual addresses (`mmap` for Posix
and `VirtualAlloc` for Windows) and allocates memory at consecutive virtual
addresses.
When a chunk of memory is requested, the allocator finds a range of
virtual pages which satisfy the requested size, changing their protection to
read and write using OS primitives (`mprotect` and `VirtualProtect`).
The physical memory is allocated on demand, when the pages are accessed.
Deallocation removes any read/write permissions from the target pages
and notifies the OS to reclaim the physical memory, while keeping the virtual
memory.
*/
struct AscendingPageAllocator
{
enum size_t pageSize = 4096;
size_t numPages;
bool valid;
// The start of the virtual address range
void* data;
// Keeps track of there the next allocation should start
void* offset;
// Number of pages which contain alive objects
size_t pagesUsed;
/**
The allocator receives as a parameter the size in pages of the virtual
address range (we assume the page size is 4096).
*/
this(size_t pages)
{
import std.exception : enforce;
valid = true;
numPages = pages;
version(Posix)
{
import core.sys.posix.sys.mman : mmap, MAP_ANON, PROT_READ,
PROT_WRITE, PROT_NONE, MAP_PRIVATE, MAP_FAILED;
data = mmap(null, pageSize * pages, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
enforce(data != MAP_FAILED, "Failed to mmap memory");
}
else version(Windows)
{
import core.sys.windows.windows : VirtualAlloc, PAGE_NOACCESS,
MEM_COMMIT, MEM_RESERVE;
data = VirtualAlloc(null, pageSize * pages, MEM_COMMIT | MEM_RESERVE, PAGE_NOACCESS);
enforce(data != null, "Failed to VirtualAlloc memory");
}
offset = data;
}
/**
Round the allocation to the next multiple of the page size.
The allocation only reserves a range of virtual pages but the actual
physical memory is allocated on demand, when accessing the memory
Returns `null` on failure or if the requested size exceeds the remaining capacity.
*/
void[] allocate(size_t n)
{
import std.exception : enforce;
size_t goodSize = goodAllocSize(n);
if (offset - data > numPages * pageSize - goodSize)
return null;
void* result = offset;
offset += goodSize;
pagesUsed += goodSize / pageSize;
// Give memory back just by changing page protection
version(Posix)
{
import core.sys.posix.sys.mman : mmap, mprotect,
MAP_PRIVATE, MAP_ANON, MAP_FAILED, PROT_NONE, PROT_WRITE, PROT_READ;
auto ret = mprotect(result, goodSize, PROT_WRITE | PROT_READ);
enforce(ret == 0, "Failed to allocate memory, mprotect failure");
}
else version(Windows)
{
import core.sys.windows.windows : VirtualProtect, PAGE_READWRITE;
uint oldProtect;
auto ret = VirtualProtect(result, goodSize, PAGE_READWRITE, &oldProtect);
enforce(ret != 0, "Failed to allocate memory, VirtualProtect failure");
}
return cast(void[]) result[0 .. n];
}
/**
Rounds the requested size to the next multiple of the page size
*/
size_t goodAllocSize(size_t n)
{
return n.roundUpToMultipleOf(pageSize);
}
/**
Removes the read/write protection of the pages which the passed buffer covers.
Hints the OS to reclaim the physical memory(``madvise`` for Posix and
``VirtualUnlock`` for Windows).
If the allocator is no longer valid, and all objects have been deallocated,
the range of virtual addresses is unmapped.
*/
version(Posix)
{
bool deallocate(void[] buf)
{
import core.sys.posix.sys.mman : posix_madvise, POSIX_MADV_DONTNEED, mprotect, PROT_NONE, munmap;
import std.exception : enforce;
size_t goodSize = goodAllocSize(buf.length);
auto ret = mprotect(buf.ptr, goodSize, PROT_NONE);
enforce(ret == 0, "Failed to deallocate memory, mprotect failure");
// madvise to let the OS reclaim the resources
ret = posix_madvise(buf.ptr, goodSize, POSIX_MADV_DONTNEED);
enforce(ret == 0, "Failed to deallocate, posix_madvise failure");
pagesUsed -= goodSize / pageSize;
if (!valid && pagesUsed == 0)
{
munmap(data, numPages * pageSize);
data = null;
}
return true;
}
}
else version(Windows)
{
bool deallocate(void[] buf)
{
import core.sys.windows.windows : VirtualUnlock, VirtualProtect,
VirtualFree, PAGE_NOACCESS, MEM_RELEASE;
import std.exception : enforce;
uint oldProtect;
size_t goodSize = goodAllocSize(buf.length);
auto ret = VirtualProtect(buf.ptr, goodSize, PAGE_NOACCESS, &oldProtect);
enforce(ret != 0, "Failed to deallocate memory, VirtualProtect failure");
// MSDN states for VirtualAlloc: Calling VirtualUnlock on a range
// of memory that is not locked releases the pages from the process's working set.
VirtualUnlock(buf.ptr, goodSize);
pagesUsed -= goodSize / pageSize;
if (!valid && pagesUsed == 0)
{
VirtualFree(data, 0, MEM_RELEASE);
data = null;
}
return true;
}
}
/**
Return ``true`` if the passed buffer is inside the range of virtual adresses.
Does not guarantee that the passed buffer is still valid.
*/
bool owns(void[] buf)
{
return buf.ptr >= data && buf.ptr < buf.ptr + numPages * pageSize;
}
/**
Marks the allocator unavailable for further allocations and sets the ``valid``
flag to ``false``, which unmaps the virtual address range when all memory is deallocated.
*/
void invalidate()
{
valid = false;
if (pagesUsed == 0) {
version(Posix)
{
import core.sys.posix.sys.mman : munmap;
munmap(data, numPages * pageSize);
}
else version(Windows)
{
import core.sys.windows.windows : VirtualFree, MEM_RELEASE;
VirtualFree(data, 0, MEM_RELEASE);
}
data = null;
}
}
/**
Returns the available size for further allocations in bytes.
*/
size_t getAvailableSize()
{
return numPages * pageSize + data - offset;
}
/**
If the passed buffer is not the last allocation, then ``delta`` can be
at most the number of bytes left on the last page.
Otherwise, we can expand the last allocation until the end of the virtual
address range.
*/
bool expand(ref void[] b, size_t delta)
{
import std.exception : enforce;
if (!delta) return true;
if (!b.ptr) return false;
size_t goodSize = goodAllocSize(b.length);
size_t bytesLeftOnPage = goodSize - b.length;
if (b.ptr + goodSize != offset && delta > bytesLeftOnPage)
return false;
size_t extraPages = 0;
if (delta > bytesLeftOnPage)
{
extraPages = goodAllocSize(delta - bytesLeftOnPage) / pageSize;
}
else
{
b = cast(void[]) b.ptr[0 .. b.length + delta];
return true;
}
if (extraPages > numPages)
return false;
if (offset - data > pageSize * (numPages - extraPages))
return false;
version(Posix)
{
import core.sys.posix.sys.mman : mprotect, PROT_READ, PROT_WRITE;
auto ret = mprotect(offset, extraPages * pageSize, PROT_READ | PROT_WRITE);
enforce(ret == 0, "Failed to expand, mprotect failure");
}
else version(Windows)
{
import core.sys.windows.windows : VirtualProtect, PAGE_READWRITE;
uint oldProtect;
auto ret = VirtualProtect(offset, extraPages * pageSize, PAGE_READWRITE, &oldProtect);
enforce(ret != 0, "Failed to expand, VirtualProtect failure");
}
pagesUsed += extraPages;
offset += extraPages * pageSize;
b = cast(void[]) b.ptr[0 .. b.length + delta];
return true;
}
/**
First it tries to ``expand`` to satisfy ``newSize``.
If not possible, it allocates a new buffer of length ``newSize``,
copies the contents and deallocates the previous buffer.
*/
bool reallocate(ref void[] b, size_t newSize)
{
if (!newSize) return deallocate(b);
if (!b) return true;
if (newSize >= b.length && expand(b, newSize - b.length))
return true;
void[] newB = allocate(newSize);
if (newB.length <= b.length) newB[] = b[0 .. newB.length];
else newB[0 .. b.length] = b[];
deallocate(b);
b = newB;
return true;
}
}
@system unittest
{
static void testrw(void[] b)
{
ubyte* buf = cast(ubyte*) b.ptr;
buf[0] = 100;
assert(buf[0] == 100);
buf[b.length - 1] = 101;
assert(buf[b.length - 1] == 101);
}
AscendingPageAllocator a = AscendingPageAllocator(4);
void[] b1 = a.allocate(1);
assert(a.getAvailableSize() == 3 * 4096);
testrw(b1);
void[] b2 = a.allocate(2);
assert(a.getAvailableSize() == 2 * 4096);
testrw(b2);
void[] b3 = a.allocate(4097);
assert(a.getAvailableSize() == 0);
testrw(b3);
assert(b1.length == 1);
assert(b2.length == 2);
assert(b3.length == 4097);
assert(a.offset - a.data == 4 * 4096);
void[] b4 = a.allocate(4);
assert(!b4);
a.invalidate();
a.deallocate(b1);
assert(a.data);
a.deallocate(b2);
assert(a.data);
a.deallocate(b3);
assert(!a.data);
}
@system unittest
{
static void testrw(void[] b)
{
ubyte* buf = cast(ubyte*) b.ptr;
buf[0] = 100;
buf[b.length - 1] = 101;
assert(buf[0] == 100);
assert(buf[b.length - 1] == 101);
}
size_t numPages = 26214;
AscendingPageAllocator a = AscendingPageAllocator(numPages);
for (int i = 0; i < numPages; i++) {
void[] buf = a.allocate(4096);
assert(buf.length == 4096);
testrw(buf);
a.deallocate(buf);
}
assert(!a.allocate(1));
assert(a.getAvailableSize() == 0);
a.invalidate();
assert(!a.data);
}
@system unittest
{
static void testrw(void[] b)
{
ubyte* buf = cast(ubyte*) b.ptr;
buf[0] = 100;
buf[b.length - 1] = 101;
assert(buf[0] == 100);
assert(buf[b.length - 1] == 101);
}
size_t numPages = 5;
enum pageSize = 4096;
AscendingPageAllocator a = AscendingPageAllocator(numPages);
void[] b1 = a.allocate(2048);
assert(b1.length == 2048);
void[] b2 = a.allocate(2048);
assert(a.expand(b1, 2048));
assert(a.expand(b1, 0));
assert(!a.expand(b1, 1));
testrw(b1);
assert(a.expand(b2, 2048));
testrw(b2);
assert(b2.length == pageSize);
assert(a.getAvailableSize() == pageSize * 3);
void[] b3 = a.allocate(2048);
assert(a.reallocate(b1, b1.length));
assert(a.reallocate(b2, b2.length));
assert(a.reallocate(b3, b3.length));
assert(b3.length == 2048);
testrw(b3);
assert(a.expand(b3, 1000));
testrw(b3);
assert(a.expand(b3, 0));
assert(b3.length == 3048);
assert(a.expand(b3, 1047));
testrw(b3);
assert(a.expand(b3, 0));
assert(b3.length == 4095);
assert(a.expand(b3, 100));
assert(a.expand(b3, 0));
assert(a.getAvailableSize() == pageSize);
assert(b3.length == 4195);
testrw(b3);
assert(a.reallocate(b1, b1.length));
assert(a.reallocate(b2, b2.length));
assert(a.reallocate(b3, b3.length));
assert(a.reallocate(b3, 2 * pageSize));
testrw(b3);
assert(a.reallocate(b1, pageSize - 1));
testrw(b1);
assert(a.expand(b1, 1));
testrw(b1);
assert(!a.expand(b1, 1));
a.invalidate();
a.deallocate(b1);
a.deallocate(b2);
a.deallocate(b3);
assert(!a.data);
}

View file

@ -298,6 +298,7 @@ SRC_STD_EXP= \
SRC_STD_EXP_ALLOC_BB= \
std\experimental\allocator\building_blocks\affix_allocator.d \
std\experimental\allocator\building_blocks\allocator_list.d \
std\experimental\allocator\building_blocks\ascending_page_allocator.d \
std\experimental\allocator\building_blocks\bitmapped_block.d \
std\experimental\allocator\building_blocks\bucketizer.d \
std\experimental\allocator\building_blocks\fallback_allocator.d \
@ -504,6 +505,7 @@ DOCS= \
$(DOC)\std_experimental_logger.html \
$(DOC)\std_experimental_allocator_building_blocks_affix_allocator.html \
$(DOC)\std_experimental_allocator_building_blocks_allocator_list.html \
$(DOC)\std_experimental_allocator_building_blocks_ascending_page_allocator.html \
$(DOC)\std_experimental_allocator_building_blocks_bitmapped_block.html \
$(DOC)\std_experimental_allocator_building_blocks_bucketizer.html \
$(DOC)\std_experimental_allocator_building_blocks_fallback_allocator.html \
@ -994,6 +996,10 @@ $(DOC)\std_experimental_allocator_building_blocks_allocator_list.html : $(STDDOC
$(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_allocator_building_blocks_allocator_list.html \
$(STDDOC) std\experimental\allocator\building_blocks\allocator_list.d
$(DOC)\std_experimental_allocator_building_blocks_ascending_page_allocator.html : $(STDDOC) std\experimental\allocator\building_blocks\ascending_page_allocator.d
$(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_allocator_building_blocks_ascending_page_allocator.html \
$(STDDOC) std\experimental\allocator\building_blocks\ascending_page_allocator.d
$(DOC)\std_experimental_allocator_building_blocks_bitmapped_block.html : $(STDDOC) std\experimental\allocator\building_blocks\bitmapped_block.d
$(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_allocator_building_blocks_bitmapped_block.html \
$(STDDOC) std\experimental\allocator\building_blocks\bitmapped_block.d

View file

@ -323,6 +323,7 @@ SRC_STD_EXP= \
SRC_STD_EXP_ALLOC_BB= \
std\experimental\allocator\building_blocks\affix_allocator.d \
std\experimental\allocator\building_blocks\allocator_list.d \
std\experimental\allocator\building_blocks\ascending_page_allocator.d \
std\experimental\allocator\building_blocks\bitmapped_block.d \
std\experimental\allocator\building_blocks\bucketizer.d \
std\experimental\allocator\building_blocks\fallback_allocator.d \
@ -529,6 +530,7 @@ DOCS= \
$(DOC)\std_experimental_logger.html \
$(DOC)\std_experimental_allocator_building_blocks_affix_allocator.html \
$(DOC)\std_experimental_allocator_building_blocks_allocator_list.html \
$(DOC)\std_experimental_allocator_building_blocks_ascending_page_allocator.html \
$(DOC)\std_experimental_allocator_building_blocks_bitmapped_block.html \
$(DOC)\std_experimental_allocator_building_blocks_bucketizer.html \
$(DOC)\std_experimental_allocator_building_blocks_fallback_allocator.html \
@ -969,6 +971,10 @@ $(DOC)\std_experimental_allocator_building_blocks_allocator_list.html : $(STDDOC
$(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_allocator_building_blocks_allocator_list.html \
$(STDDOC) std\experimental\allocator\building_blocks\allocator_list.d
$(DOC)\std_experimental_allocator_building_blocks_ascending_page_allocator.html : $(STDDOC) std\experimental\allocator\building_blocks\ascending_page_allocator.d
$(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_allocator_building_blocks_ascending_page_allocator.html \
$(STDDOC) std\experimental\allocator\building_blocks\ascending_page_allocator.d
$(DOC)\std_experimental_allocator_building_blocks_bitmapped_block.html : $(STDDOC) std\experimental\allocator\building_blocks\bitmapped_block.d
$(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_allocator_building_blocks_bitmapped_block.html \
$(STDDOC) std\experimental\allocator\building_blocks\bitmapped_block.d