Reworked Zone memory allocator a bit.

This commit is contained in:
kobalicek
2014-05-29 14:25:40 +02:00
parent 6def28a509
commit 5869dc4736
6 changed files with 208 additions and 152 deletions

View File

@@ -29,7 +29,7 @@ CodeGen::CodeGen(Runtime* runtime) :
_features(static_cast<uint8_t>(IntUtil::mask(kCodeGenOptimizedAlign))), _features(static_cast<uint8_t>(IntUtil::mask(kCodeGenOptimizedAlign))),
_error(kErrorOk), _error(kErrorOk),
_options(0), _options(0),
_baseZone(16384 - sizeof(Zone::Chunk) - kMemAllocOverhead) {} _baseZone(16384 - kZoneOverhead) {}
CodeGen::~CodeGen() { CodeGen::~CodeGen() {
if (_errorHandler != NULL) if (_errorHandler != NULL)

View File

@@ -44,9 +44,9 @@ BaseCompiler::BaseCompiler(Runtime* runtime) :
_lastNode(NULL), _lastNode(NULL),
_cursor(NULL), _cursor(NULL),
_func(NULL), _func(NULL),
_varZone(4096 - kMemAllocOverhead), _varZone(4096 - kZoneOverhead),
_stringZone(4096 - kMemAllocOverhead), _stringZone(4096 - kZoneOverhead),
_localConstZone(4096 - kMemAllocOverhead), _localConstZone(4096 - kZoneOverhead),
_localConstPool(&_localConstZone), _localConstPool(&_localConstZone),
_globalConstPool(&_baseZone) {} _globalConstPool(&_baseZone) {}

View File

@@ -22,7 +22,7 @@ namespace asmjit {
BaseContext::BaseContext(BaseCompiler* compiler) : BaseContext::BaseContext(BaseCompiler* compiler) :
_compiler(compiler), _compiler(compiler),
_baseZone(8192 - sizeof(Zone::Chunk) - kMemAllocOverhead) { _baseZone(8192 - kZoneOverhead) {
BaseContext::reset(); BaseContext::reset();
} }

View File

@@ -29,32 +29,39 @@
// efficient. There are several goals I decided to write implementation myself. // efficient. There are several goals I decided to write implementation myself.
// //
// Goals: // Goals:
// - We need usually to allocate blocks of 64 bytes long and more.
// - Alignment of allocated blocks is large - 32 bytes or 64 bytes.
// - Keep memory manager information outside allocated virtual memory pages
// (these pages allows execution of code).
// - Keep implementation small.
// //
// I think that implementation is not small and probably not too much readable, // - Granularity of allocated blocks is different than granularity for a typical
// so there is small know how. // C malloc. It is at least 64-bytes so Assembler/Compiler can guarantee the
// alignment required. Alignment requirements can grow in the future, but at
// the moment 64 bytes is safe (we may jump to 128 bytes if necessary or make
// it configurable).
// //
// - Implementation is based on bit arrays and binary trees. Bit arrays // - Keep memory manager information outside of the allocated virtual memory
// contains information about allocated and unused blocks of memory. Each // pages, because these pages allow executing of machine code and there should
// block size describes MemNode::density member. Count of blocks are // not be data required to keep track of these blocks. Another reason is that
// stored in MemNode::blocks member. For example if density is 64 and // some environments (i.e. iOS) allow to generate and run JIT code, but this
// count of blocks is 20, memory node contains 64*20 bytes of memory and // code has to be set to [Executable, but not Writable].
// smallest possible allocation (and also alignment) is 64 bytes. So density //
// describes also memory alignment. Binary trees are used to enable fast // - Keep implementation simple and easy to follow.
// lookup into all addresses allocated by memory manager instance. This is //
// used mainly in VMemPrivate::release(). // Implementation is based on bit arrays and binary trees. Bit arrays contain
// information related to allocated and unused blocks of memory. The size of
// a block is described by `MemNode::density`. Count of blocks is stored in
// `MemNode::blocks`. For example if density is 64 and count of blocks is 20,
// memory node contains 64*20 bytes of memory and smallest possible allocation
// (and also alignment) is 64 bytes. So density is also related to memory
// alignment. Binary trees (RB) are used to enable fast lookup into all addresses
// allocated by memory manager instance. This is used mainly by `VMemPrivate::release()`.
//
// Bit array looks like this (empty = unused, X = used) - Size of block 64:
// //
// Bit array looks like this (empty = unused, X = used) - Size of block 64
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// | |X|X| | | | | |X|X|X|X|X|X| | | | | | | | | | | | |X| | | | |X|X|X| | | // | |X|X| | | | | |X|X|X|X|X|X| | | | | | | | | | | | |X| | | | |X|X|X| | |
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Bits array shows that there are 12 allocated blocks of 64 bytes, so total // (Maximum continuous block)
// allocated size is 768 bytes. Maximum count of continuous blocks is 12 //
// (see largest gap). // These bits show that there are 12 allocated blocks (X) of 64 bytes, so total
// size allocated is 768 bytes. Maximum count of continuous memory is 12 * 64.
namespace asmjit { namespace asmjit {

View File

@@ -19,18 +19,18 @@
namespace asmjit { namespace asmjit {
//! Zero width chunk used when Zone doesn't have any memory allocated. //! Zero size block used by `Zone` that doesn't have any memory allocated.
static const Zone::Chunk Zone_zeroChunk = { static const Zone::Block Zone_zeroBlock = {
NULL, 0, 0, { 0 } NULL, NULL, NULL, NULL, { 0 }
}; };
// ============================================================================ // ============================================================================
// [asmjit::Zone - Construction / Destruction] // [asmjit::Zone - Construction / Destruction]
// ============================================================================ // ============================================================================
Zone::Zone(size_t chunkSize) { Zone::Zone(size_t blockSize) {
_chunks = const_cast<Zone::Chunk*>(&Zone_zeroChunk); _blocks = const_cast<Zone::Block*>(&Zone_zeroBlock);
_chunkSize = chunkSize; _blockSize = blockSize;
} }
Zone::~Zone() { Zone::~Zone() {
@@ -42,35 +42,33 @@ Zone::~Zone() {
// ============================================================================ // ============================================================================
void Zone::clear() { void Zone::clear() {
Chunk* cur = _chunks; Block* cur = _blocks;
if (cur == &Zone_zeroChunk) // Can't be altered.
if (cur == &Zone_zeroBlock)
return; return;
while (cur->prev != NULL)
cur = cur->prev; cur = cur->prev;
while (cur != NULL) {
Chunk* prev = cur->prev;
::free(cur);
cur = prev;
}
_chunks->pos = 0; cur->pos = cur->data;
_chunks->prev = NULL; _blocks = cur;
} }
void Zone::reset() { void Zone::reset() {
Chunk* cur = _chunks; Block* cur = _blocks;
if (cur == &Zone_zeroChunk) // Can't be altered.
if (cur == &Zone_zeroBlock)
return; return;
while (cur != NULL) { do {
Chunk* prev = cur->prev; Block* prev = cur->prev;
::free(cur); ::free(cur);
cur = prev; cur = prev;
} } while (cur != NULL);
_chunks = const_cast<Zone::Chunk*>(&Zone_zeroChunk); _blocks = const_cast<Zone::Block*>(&Zone_zeroBlock);
} }
// ============================================================================ // ============================================================================
@@ -78,38 +76,58 @@ void Zone::reset() {
// ============================================================================ // ============================================================================
void* Zone::_alloc(size_t size) { void* Zone::_alloc(size_t size) {
Chunk* cur = _chunks; Block* curBlock = _blocks;
ASMJIT_ASSERT(cur == &Zone_zeroChunk || cur->getRemainingSize() < size); size_t blockSize = IntUtil::iMax<size_t>(_blockSize, size);
size_t chunkSize = _chunkSize; // The `_alloc()` method can only be called if there is not enough space
if (chunkSize < size) // in the current block, see `alloc()` implementation for more details.
chunkSize = size; ASMJIT_ASSERT(curBlock == &Zone_zeroBlock || curBlock->getRemainingSize() < size);
cur = static_cast<Chunk*>(::malloc(sizeof(Chunk) - sizeof(void*) + chunkSize)); // If the `Zone` has been cleared the current block doesn't have to be the
if (cur == NULL) // last one. Check if there is a block that can be used instead of allocating
// a new one. If there is a `next` block it's completely unused, we don't have
// to check for remaining bytes.
Block* next = curBlock->next;
if (next != NULL && next->getBlockSize() >= size) {
next->pos = next->data + size;
_blocks = next;
return static_cast<void*>(next->data);
}
// Prevent arithmetic overflow.
if (blockSize > ~static_cast<size_t>(0) - sizeof(Block))
return NULL; return NULL;
cur->prev = NULL; Block* newBlock = static_cast<Block*>(::malloc(sizeof(Block) - sizeof(void*) + blockSize));
cur->pos = 0; if (newBlock == NULL)
cur->size = chunkSize; return NULL;
if (_chunks != &Zone_zeroChunk) newBlock->pos = newBlock->data + size;
cur->prev = _chunks; newBlock->end = newBlock->data + blockSize;
_chunks = cur; newBlock->prev = NULL;
newBlock->next = NULL;
uint8_t* p = cur->data + cur->pos; if (curBlock != &Zone_zeroBlock) {
cur->pos += size; newBlock->prev = curBlock;
curBlock->next = newBlock;
ASMJIT_ASSERT(cur->pos <= cur->size); // Does only happen if there is a next block, but the requested memory
return (void*)p; // can't fit into it. In this case a new buffer is allocated and inserted
// between the current block and the next one.
if (next != NULL) {
newBlock->next = next;
next->prev = newBlock;
}
}
_blocks = newBlock;
return static_cast<void*>(newBlock->data);
} }
void* Zone::_allocZeroed(size_t size) { void* Zone::allocZeroed(size_t size) {
void* p = _alloc(size); void* p = alloc(size);
if (p != NULL) if (p != NULL)
::memset(p, 0, size); ::memset(p, 0, size);
return p; return p;
} }
@@ -132,7 +150,7 @@ char* Zone::sdup(const char* str) {
if (str == NULL) if (str == NULL)
return NULL; return NULL;
size_t len = strlen(str); size_t len = ::strlen(str);
if (len == 0) if (len == 0)
return NULL; return NULL;
@@ -153,17 +171,16 @@ char* Zone::sformat(const char* fmt, ...) {
if (fmt == NULL) if (fmt == NULL)
return NULL; return NULL;
char buf[256]; char buf[512];
size_t len; size_t len;
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
len = vsnprintf(buf, 256, fmt, ap);
va_end(ap);
len = IntUtil::iMin<size_t>(len, 255); len = vsnprintf(buf, ASMJIT_ARRAY_SIZE(buf) - 1, fmt, ap);
buf[len++] = 0; buf[len++] = 0;
va_end(ap);
return static_cast<char*>(dup(buf, len)); return static_cast<char*>(dup(buf, len));
} }

View File

@@ -23,28 +23,53 @@ namespace asmjit {
// [asmjit::Zone] // [asmjit::Zone]
// ============================================================================ // ============================================================================
//! Fast incremental memory allocator. //! Zone memory allocator.
//! //!
//! Memory allocator designed to allocate small objects that will be invalidated //! Zone is an incremental memory allocator that allocates memory by simply
//! (freed) all at once. //! incrementing a pointer. It allocates blocks of memory by using standard
//! C library `malloc/free`, but divides these blocks into smaller segments
//! requirested by calling `Zone::alloc()` and friends.
//!
//! Zone memory allocators are designed to allocate data of short lifetime. The
//! data used by `Assembler` and `Compiler` has a very short lifetime, thus, is
//! allocated by `Zone`. The advantage is that `Zone` can free all of the data
//! allocated at once by calling `clear()` or `reset()`.
struct Zone { struct Zone {
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// [Chunk] // [Block]
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
//! \internal //! \internal
//! //!
//! One allocated chunk of memory. //! A single block of memory.
struct Chunk { struct Block {
//! Get count of remaining (unused) bytes in chunk. // ------------------------------------------------------------------------
ASMJIT_INLINE size_t getRemainingSize() const { return size - pos; } // [Accessors]
// ------------------------------------------------------------------------
//! Link to previous chunk. //! Get the size of the block.
Chunk* prev; ASMJIT_INLINE size_t getBlockSize() const {
//! Position in this chunk. return (size_t)(end - data);
size_t pos; }
//! Size of this chunk (in bytes).
size_t size; //! Get count of remaining bytes in the block.
ASMJIT_INLINE size_t getRemainingSize() const {
return (size_t)(end - pos);
}
// ------------------------------------------------------------------------
// [Members]
// ------------------------------------------------------------------------
//! Current data pointer (pointer to the first available byte).
uint8_t* pos;
//! End data pointer (pointer to the first invalid byte).
uint8_t* end;
//! Link to the previous block.
Block* prev;
//! Link to the next block.
Block* next;
//! Data. //! Data.
uint8_t data[sizeof(void*)]; uint8_t data[sizeof(void*)];
@@ -56,38 +81,45 @@ struct Zone {
//! Create a new instance of `Zone` allocator. //! Create a new instance of `Zone` allocator.
//! //!
//! The `chunkSize` parameter describes the size of the chunk. If `alloc()` //! The `blockSize` parameter describes the default size of the block. If the
//! requires more memory than `chunkSize` then a bigger chunk will be //! `size` parameter passed to `alloc()` is greater than the default size
//! allocated, however `chunkSize` will not be changed. //! `Zone` will allocate and use a larger block, but it will not change the
ASMJIT_API Zone(size_t chunkSize); //! default `blockSize`.
//! Destroy `Zone` instance.
//! //!
//! Destructor released all chunks allocated by `Zone`. The `reset()` member //! It's not required, but it's good practice to set `blockSize` to a
//! function does the same without actually destroying `Zone` object itself. //! reasonable value that depends on the usage of `Zone`. Greater block sizes
//! are generally safer and performs better than unreasonably low values.
ASMJIT_API Zone(size_t blockSize);
//! Destroy the `Zone` instance.
//!
//! This will destroy the `Zone` instance and release all blocks of memory
//! allocated by it. It performs implicit `reset()`.
ASMJIT_API ~Zone(); ASMJIT_API ~Zone();
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// [Clear / Reset] // [Clear / Reset]
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
//! Reset the `Zone` releasing all chunks allocated. //! Clear the `Zone`, but keep all blocks allocated so they can be reused.
//! //!
//! Calling `clear()` will release all chunks allocated by `Zone` except the //! This is the preferred way of invalidating objects allocated by `Zone`.
//! first one that will be reused if needed.
ASMJIT_API void clear(); ASMJIT_API void clear();
//! Reset the `Zone` releasing all chunks allocated. //! Reset the `Zone` releasing all blocks allocated.
//! //!
//! Calling `reset()` will release all chunks allocated by `Zone`. //! Calling `reset()` does complete cleanup, it releases all blocks allocated
//! by `Zone`.
ASMJIT_API void reset(); ASMJIT_API void reset();
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// [Accessors] // [Accessors]
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
//! Get chunk size. //! Get the default block size.
ASMJIT_INLINE size_t getChunkSize() const { return _chunkSize; } ASMJIT_INLINE size_t getBlockSize() const {
return _blockSize;
}
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// [Alloc] // [Alloc]
@@ -95,75 +127,70 @@ struct Zone {
//! Allocate `size` bytes of memory. //! Allocate `size` bytes of memory.
//! //!
//! Pointer allocated by this way will be valid until `Zone` object is //! Pointer returned is valid until the `Zone` instance is destroyed or reset
//! destroyed. To create class by this way use placement `new` and `delete` //! by calling `clear()` or `reset()`. If you plan to make an instance of C++
//! operators: //! from the given pointer use placement `new` and `delete` operators:
//! //!
//! ~~~ //! ~~~
//! // Example of simple class allocation. //! using namespace asmjit;
//! using namespace asmjit
//! //!
//! // Your class. //! class SomeObject { ... };
//! class Object {
//! // members...
//! };
//! //!
//! // Your function //! // Create Zone with default block size of 65536 bytes.
//! void f() {
//! // Create zone object with chunk size of 65536 bytes.
//! Zone zone(65536); //! Zone zone(65536);
//! //!
//! // Create your objects using zone object allocating, for example: //! // Create your objects using zone object allocating, for example:
//! Object* obj = new(zone.alloc(sizeof(YourClass))) Object(); //! Object* obj = static_cast<Object*>( zone.alloc(sizeof(SomeClass)) );
//
//! if (obj == NULL) {
//! // Handle out of memory error.
//! }
//!
//! // To instantiate class placement `new` and `delete` operators can be used.
//! new(obj) Object();
//! //!
//! // ... lifetime of your objects ... //! // ... lifetime of your objects ...
//! //!
//! // Destroy your objects: //! // To destroy the instance (if required).
//! obj->~Object(); //! obj->~Object();
//! //!
//! // Zone destructor will free all memory allocated through it, you can //! // Reset of destroy `Zone`.
//! // call `zone.reset()` if you wan't to reuse current `Zone`. //! zone.reset();
//! }
//! ~~~ //! ~~~
ASMJIT_INLINE void* alloc(size_t size) { ASMJIT_INLINE void* alloc(size_t size) {
Chunk* cur = _chunks; Block* cur = _blocks;
if (cur->getRemainingSize() < size) uint8_t* ptr = cur->pos;
size_t remainingBytes = (size_t)(cur->end - ptr);
if (remainingBytes < size)
return _alloc(size); return _alloc(size);
uint8_t* p = cur->data + cur->pos;
cur->pos += size; cur->pos += size;
ASMJIT_ASSERT(cur->pos <= cur->size); ASMJIT_ASSERT(cur->pos <= cur->end);
return (void*)p; return (void*)ptr;
} }
//! Like `alloc()`, but the return is casted to `T*`. //! Allocate `size` bytes of zeroed memory.
//!
//! See \ref alloc() for more details.
ASMJIT_API void* allocZeroed(size_t size);
//! Like `alloc()`, but the return pointer is casted to `T*`.
template<typename T> template<typename T>
ASMJIT_INLINE T* allocT(size_t size = sizeof(T)) { ASMJIT_INLINE T* allocT(size_t size = sizeof(T)) {
return static_cast<T*>(alloc(size)); return static_cast<T*>(alloc(size));
} }
//! \internal //! Like `allocZeroed()`, but the return pointer is casted to `T*`.
ASMJIT_API void* _alloc(size_t size); template<typename T>
ASMJIT_INLINE T* allocZeroedT(size_t size = sizeof(T)) {
//! Allocate `size` bytes of zeroed memory. return static_cast<T*>(allocZeroed(size));
ASMJIT_INLINE void* allocZeroed(size_t size) {
Chunk* cur = _chunks;
if (cur->getRemainingSize() < size)
return _allocZeroed(size);
uint8_t* p = cur->data + cur->pos;
cur->pos += size;
ASMJIT_ASSERT(cur->pos <= cur->size);
::memset(p, 0, size);
return (void*)p;
} }
//! \internal //! \internal
ASMJIT_API void* _allocZeroed(size_t size); ASMJIT_API void* _alloc(size_t size);
//! Helper to duplicate data. //! Helper to duplicate data.
ASMJIT_API void* dup(const void* data, size_t size); ASMJIT_API void* dup(const void* data, size_t size);
@@ -178,10 +205,15 @@ struct Zone {
// [Members] // [Members]
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
//! Last allocated chunk of memory. //! The current block.
Chunk* _chunks; Block* _blocks;
//! Default chunk size. //! Default block size.
size_t _chunkSize; size_t _blockSize;
};
enum {
//! Zone allocator overhead.
kZoneOverhead = static_cast<int>(sizeof(Zone::Block) - sizeof(void*)) + kMemAllocOverhead
}; };
//! \} //! \}