mirror of
https://github.com/AquariaOSE/Aquaria.git
synced 2024-12-25 06:05:45 +00:00
Add special memory allocator for Lua that should take some memory stress away from heavy scripting.
This commit is contained in:
parent
c857722213
commit
69890093bd
5 changed files with 479 additions and 2 deletions
|
@ -9952,7 +9952,7 @@ static const struct {
|
|||
//============================================================================================
|
||||
|
||||
ScriptInterface::ScriptInterface()
|
||||
: baseState(NULL)
|
||||
: baseState(NULL), _sballoc(8, 128)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -9975,9 +9975,15 @@ void ScriptInterface::reset()
|
|||
init();
|
||||
}
|
||||
|
||||
void *ScriptInterface::the_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
|
||||
{
|
||||
ScriptInterface *this_ = (ScriptInterface*)ud;
|
||||
return this_->_sballoc.Alloc(ptr, nsize, osize);
|
||||
}
|
||||
|
||||
lua_State *ScriptInterface::createLuaVM()
|
||||
{
|
||||
lua_State *state = lua_open(); /* opens Lua */
|
||||
lua_State *state = lua_newstate(the_alloc, this); /* opens Lua */
|
||||
luaopen_base(state); /* opens the basic library */
|
||||
luaopen_table(state); /* opens the table library */
|
||||
luaopen_string(state); /* opens the string lib. */
|
||||
|
|
|
@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|||
#define SCRIPTINTERFACE_H
|
||||
|
||||
#include "../BBGE/Base.h"
|
||||
#include "../BBGE/MemoryAllocatorSmallBlock.h"
|
||||
|
||||
struct lua_State;
|
||||
|
||||
|
@ -105,8 +106,10 @@ protected:
|
|||
void destroyLuaVM(lua_State *state);
|
||||
lua_State *createLuaThread(const std::string &file);
|
||||
int destroyLuaThread(const std::string &file, lua_State *thread);
|
||||
static void *the_alloc(void *ud, void *ptr, size_t osize, size_t nsize);
|
||||
|
||||
lua_State *baseState;
|
||||
SmallBlockAllocator _sballoc;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
284
BBGE/MemoryAllocatorSmallBlock.cpp
Normal file
284
BBGE/MemoryAllocatorSmallBlock.cpp
Normal file
|
@ -0,0 +1,284 @@
|
|||
// Public domain
|
||||
|
||||
// Aquaria specific...
|
||||
#include "Base.h"
|
||||
|
||||
#include "algorithmx.h"
|
||||
#include "MemoryAllocatorSmallBlock.h"
|
||||
#include "bithacks.h"
|
||||
|
||||
//#define DD(...) logdev(__VA_ARGS__)
|
||||
#define DD(...)
|
||||
#define logdev(...)
|
||||
#define logerror(...)
|
||||
#define ASSERT(x) assert(x)
|
||||
|
||||
|
||||
SmallBlockAllocator::SmallBlockAllocator(unsigned int blockSizeMin,
|
||||
unsigned int blockSizeMax,
|
||||
unsigned int blockSizeIncr /* = 8 */,
|
||||
unsigned int elemsPerBlockMin /* = 64 */,
|
||||
unsigned int elemsPerBlockMax /* = 2048 */)
|
||||
: _blockSizeMin(blockSizeMin)
|
||||
, _blockSizeMax(blockSizeMax)
|
||||
, _blockSizeIncr(blockSizeIncr)
|
||||
, _elemsPerBlockMin(elemsPerBlockMin)
|
||||
, _elemsPerBlockMax(elemsPerBlockMax)
|
||||
{
|
||||
ASSERT(_blockSizeIncr % 4 == 0); // less than 4 bytes makes no sense
|
||||
ASSERT(_blockSizeMin % _blockSizeIncr == 0);
|
||||
ASSERT(_blockSizeMax % _blockSizeIncr == 0);
|
||||
ASSERT((_blockSizeMax - _blockSizeMin) % _blockSizeIncr == 0);
|
||||
unsigned int c = ((_blockSizeMax - _blockSizeMin) / _blockSizeIncr) + 1;
|
||||
logdev("SBA: Using %u distinct block sizes from %u - %u bytes", c, _blockSizeMin, _blockSizeMax);
|
||||
_blocks = new Block*[c]; // TODO: Do we really want to use dynamic allocation here?
|
||||
memset(_blocks, 0, c * sizeof(Block*));
|
||||
}
|
||||
|
||||
SmallBlockAllocator::~SmallBlockAllocator()
|
||||
{
|
||||
while(_allblocks.size())
|
||||
{
|
||||
Block *blk = _allblocks.back();
|
||||
logerror("~SmallBlockAllocator(): Warning: Leftover block with %u/%u elements, %uB each",
|
||||
blk->maxElems, blk->maxElems - blk->freeElems, blk->elemSize);
|
||||
_FreeBlock(blk);
|
||||
}
|
||||
delete [] _blocks;
|
||||
}
|
||||
|
||||
void *SmallBlockAllocator::Alloc(void *ptr, size_t newsize, size_t oldsize)
|
||||
{
|
||||
DD("SBA::Alloc() ptr = %p; newsize = %u, oldsize = %u", ptr, newsize, oldsize);
|
||||
|
||||
if(ptr)
|
||||
{
|
||||
if(!newsize)
|
||||
{
|
||||
_Free(ptr, oldsize);
|
||||
return NULL;
|
||||
}
|
||||
else if(newsize == oldsize)
|
||||
return ptr;
|
||||
else
|
||||
return _Realloc(ptr, newsize, oldsize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(newsize)
|
||||
return _Alloc(newsize);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SmallBlockAllocator::Block *SmallBlockAllocator::_AllocBlock(unsigned int elemCount, unsigned int elemSize)
|
||||
{
|
||||
DD("SBA: _AllocBlock: elemCount = %u, elemSize = %u", elemCount, elemSize);
|
||||
|
||||
const unsigned int bitsPerInt = (sizeof(unsigned int) * 8); // 32
|
||||
unsigned int bitmapInts = (elemCount + (bitsPerInt - 1)) / bitsPerInt;
|
||||
void *ptr = malloc(
|
||||
(sizeof(Block) - sizeof(unsigned int)) // block header without bitmap[1]
|
||||
+ (bitmapInts * sizeof(unsigned int)) // actual bitmap size
|
||||
+ (elemCount * elemSize) // data size
|
||||
);
|
||||
|
||||
if(!ptr)
|
||||
return NULL;
|
||||
Block *blk = (Block*)ptr;
|
||||
memset(&blk->bitmap[0], 0xff, bitmapInts * sizeof(unsigned int)); // all free
|
||||
blk->elemSize = elemSize;
|
||||
blk->maxElems = elemCount;
|
||||
blk->freeElems = elemCount;
|
||||
blk->bitmapInts = bitmapInts;
|
||||
blk->next = NULL;
|
||||
blk->prev = NULL;
|
||||
|
||||
// using insertion sort
|
||||
std::vector<Block*>::iterator insertit = std::lower_bound(_allblocks.begin(), _allblocks.end(), blk);
|
||||
_allblocks.insert(insertit, blk);
|
||||
|
||||
return blk;
|
||||
}
|
||||
|
||||
void SmallBlockAllocator::_FreeBlock(Block *blk)
|
||||
{
|
||||
DD("SBA: _FreeBlock: elemCount = %u, elemSize = %u", blk->maxElems, blk->elemSize);
|
||||
|
||||
if(blk->prev)
|
||||
blk->prev->next = blk->next;
|
||||
else
|
||||
_blocks[GetIndexForElemSize(blk->elemSize)] = blk->next;
|
||||
|
||||
if(blk->next)
|
||||
blk->next->prev = blk->prev;
|
||||
|
||||
free(blk);
|
||||
|
||||
// keeps the vector sorted
|
||||
_allblocks.erase(std::remove(_allblocks.begin(), _allblocks.end(), blk), _allblocks.end());
|
||||
}
|
||||
|
||||
|
||||
SmallBlockAllocator::Block *SmallBlockAllocator::_AppendBlock(unsigned int elemSize)
|
||||
{
|
||||
unsigned int idx = GetIndexForElemSize(elemSize);
|
||||
Block *blk = _blocks[idx];
|
||||
unsigned int elemsPerBlock = _elemsPerBlockMin;
|
||||
if(blk)
|
||||
{
|
||||
while(blk->next)
|
||||
blk = blk->next;
|
||||
elemsPerBlock = blk->maxElems * 2; // new block is double the size
|
||||
if(elemsPerBlock > _elemsPerBlockMax)
|
||||
elemsPerBlock = _elemsPerBlockMax;
|
||||
}
|
||||
|
||||
unsigned int blockElemSize = ((elemSize + (_blockSizeIncr - 1)) / _blockSizeIncr) * _blockSizeIncr;
|
||||
ASSERT(blockElemSize >= elemSize);
|
||||
|
||||
Block *newblk = _AllocBlock(elemsPerBlock, blockElemSize);
|
||||
if(!newblk)
|
||||
return NULL;
|
||||
|
||||
if(blk)
|
||||
{
|
||||
blk->next = newblk; // append to list
|
||||
newblk->prev = blk;
|
||||
}
|
||||
else
|
||||
_blocks[idx] = newblk; // list head
|
||||
|
||||
return newblk;
|
||||
}
|
||||
|
||||
SmallBlockAllocator::Block *SmallBlockAllocator::_GetFreeBlock(unsigned int elemSize)
|
||||
{
|
||||
unsigned int idx = GetIndexForElemSize(elemSize);
|
||||
Block *blk = _blocks[idx];
|
||||
while(blk && !blk->freeElems)
|
||||
blk = blk->next;
|
||||
return blk;
|
||||
}
|
||||
|
||||
void *SmallBlockAllocator::Block::allocElem()
|
||||
{
|
||||
ASSERT(freeElems);
|
||||
unsigned int i = 0;
|
||||
for( ; !bitmap[i]; ++i) // as soon as one isn't all zero, there's a free slot
|
||||
ASSERT(i < bitmapInts);
|
||||
ASSERT(i < bitmapInts);
|
||||
int freeidx = bithacks::ctz(bitmap[i]);
|
||||
ASSERT(bitmap[i] & (1 << freeidx)); // make sure this is '1' (= free)
|
||||
bitmap[i] &= ~(1 << freeidx); // put '0' where '1' was (-> mark as non-free)
|
||||
--freeElems;
|
||||
const unsigned int offs = (i * sizeof(unsigned int) * 8 * elemSize); // skip forward i bitmaps (32 elems each)
|
||||
unsigned char *ret = getPtr() + offs + (elemSize * freeidx);
|
||||
ASSERT(contains(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SmallBlockAllocator::Block::contains(unsigned char *ptr) const
|
||||
{
|
||||
const unsigned char *pp = getPtr();
|
||||
|
||||
if(ptr < pp)
|
||||
return false; // pointer is out of range (1)
|
||||
if(ptr >= pp + (maxElems * elemSize))
|
||||
return false; // pointer is out of range (2)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SmallBlockAllocator::Block::freeElem(unsigned char *ptr)
|
||||
{
|
||||
ASSERT(contains(ptr));
|
||||
ASSERT(freeElems < maxElems); // make sure the block is not all free
|
||||
|
||||
const ptrdiff_t p = ptr - getPtr();
|
||||
ASSERT((p % elemSize) == 0); // make sure alignment is right
|
||||
const unsigned int idx = p / elemSize;
|
||||
const unsigned int bitsPerInt = sizeof(unsigned int) * 8; // 32
|
||||
const unsigned int bitmapIdx = idx / bitsPerInt;
|
||||
const unsigned int bitIdx = idx % bitsPerInt;
|
||||
ASSERT(bitmapIdx < bitmapInts);
|
||||
ASSERT(!(bitmap[bitmapIdx] & (1 << bitIdx))); // make sure this is '0' (= used)
|
||||
|
||||
bitmap[bitmapIdx] |= (1 << bitIdx); // put '1' where '0' was (-> mark as free)
|
||||
++freeElems;
|
||||
|
||||
#ifdef _DEBUG
|
||||
memset(ptr, 0xfa, elemSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
void *SmallBlockAllocator::_FallbackAlloc(unsigned int size)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
void SmallBlockAllocator::_FallbackFree(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
void *SmallBlockAllocator::_Alloc(unsigned int size)
|
||||
{
|
||||
if(size > _blockSizeMax)
|
||||
return _FallbackAlloc(size);
|
||||
|
||||
Block *blk = _GetFreeBlock(size);
|
||||
ASSERT(!blk || blk->freeElems);
|
||||
if(!blk)
|
||||
{
|
||||
blk = _AppendBlock(size);
|
||||
if(!blk)
|
||||
return _FallbackAlloc(size);
|
||||
}
|
||||
return blk->allocElem();
|
||||
}
|
||||
|
||||
bool SmallBlockAllocator::Block_ptr_cmp(const Block *blk, const void *ptr)
|
||||
{
|
||||
return blk->getEndPtr() < ((unsigned char*)ptr);
|
||||
}
|
||||
|
||||
SmallBlockAllocator::Block *SmallBlockAllocator::_FindBlockContainingPtr(void *ptr)
|
||||
{
|
||||
// MSVC's std::lower_bound uses iterator debug checks in debug mode,
|
||||
// which breaks Block_ptr_cmp() because the left and right types are different.
|
||||
std::vector<Block*>::iterator it = stdx_fg::lower_bound(_allblocks.begin(), _allblocks.end(), ptr, Block_ptr_cmp);
|
||||
return (it != _allblocks.end() && (*it)->contains((unsigned char*)ptr)) ? *it : NULL;
|
||||
}
|
||||
|
||||
void SmallBlockAllocator::_Free(void *ptr, unsigned int size)
|
||||
{
|
||||
if(size <= _blockSizeMax)
|
||||
{
|
||||
Block *blk = _FindBlockContainingPtr(ptr);
|
||||
if(blk)
|
||||
{
|
||||
ASSERT(blk->elemSize >= size); // ptr might be from a larger block in case _Realloc() failed to shrink
|
||||
blk->freeElem((unsigned char*)ptr);
|
||||
if(blk->freeElems == blk->maxElems)
|
||||
_FreeBlock(blk); // remove if completely unused
|
||||
return;
|
||||
}
|
||||
}
|
||||
_FallbackFree(ptr);
|
||||
}
|
||||
|
||||
void *SmallBlockAllocator::_Realloc(void *ptr, unsigned int newsize, unsigned int oldsize)
|
||||
{
|
||||
void *newptr = _Alloc(newsize);
|
||||
|
||||
// If the new allocation failed, just re-use the old pointer if it was a shrink request
|
||||
// This also satisfies Lua, which assumes that realloc() shrink requests cannot fail
|
||||
if(!newptr)
|
||||
return newsize <= oldsize ? ptr : NULL;
|
||||
|
||||
memcpy(newptr, ptr, std::min(oldsize, newsize));
|
||||
_Free(ptr, oldsize);
|
||||
return newptr;
|
||||
}
|
||||
|
100
BBGE/MemoryAllocatorSmallBlock.h
Normal file
100
BBGE/MemoryAllocatorSmallBlock.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
#ifndef MEMORY_ALLOCATOR_SMALL_BLOCK_H
|
||||
#define MEMORY_ALLOCATOR_SMALL_BLOCK_H
|
||||
|
||||
/* Optimized memory allocator for small & frequent (de-)allocations.
|
||||
* Low memory overhead. Used for Lua.
|
||||
* Inspired by http://dns.achurch.org/cgi-bin/hg/aquaria-psp/file/tip/PSP/src/lalloc.c
|
||||
*/
|
||||
|
||||
// Originally made for LV3proj_ng (https://github.com/fgenesis/lv3proj_ng)
|
||||
// Hacked in shape for use in Aquaria
|
||||
// Public domain
|
||||
|
||||
|
||||
#include <vector>
|
||||
|
||||
class SmallBlockAllocator
|
||||
{
|
||||
public:
|
||||
|
||||
SmallBlockAllocator(unsigned int blockSizeMin, unsigned int blockSizeMax, unsigned int blockSizeIncr = 8,
|
||||
unsigned int elemsPerBlockMin = 64, unsigned int elemsPerBlockMax = 2048);
|
||||
|
||||
~SmallBlockAllocator();
|
||||
|
||||
void *Alloc(void *ptr, size_t newsize, size_t oldsize);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void *_Alloc(unsigned int size);
|
||||
void *_Realloc(void *ptr, unsigned int newsize, unsigned int oldsize);
|
||||
void _Free(void* ptr, unsigned int size);
|
||||
|
||||
void *_FallbackAlloc(unsigned int size);
|
||||
void _FallbackFree(void *ptr);
|
||||
|
||||
struct Block
|
||||
{
|
||||
// block header start
|
||||
Block *next;
|
||||
Block *prev;
|
||||
unsigned short maxElems;
|
||||
unsigned short freeElems;
|
||||
unsigned short elemSize;
|
||||
unsigned short bitmapInts;
|
||||
// block header end
|
||||
|
||||
unsigned int bitmap[1]; // variable sized
|
||||
// actual storage memory starts after bitmap[bitmapInts]
|
||||
|
||||
inline unsigned char *getPtr()
|
||||
{
|
||||
return (unsigned char*)&bitmap[bitmapInts];
|
||||
}
|
||||
inline const unsigned char *getPtr() const
|
||||
{
|
||||
return (unsigned char*)&bitmap[bitmapInts];
|
||||
}
|
||||
inline unsigned char *getEndPtr()
|
||||
{
|
||||
return getPtr() + (maxElems * elemSize);
|
||||
}
|
||||
inline const unsigned char *getEndPtr() const
|
||||
{
|
||||
return getPtr() + (maxElems * elemSize);
|
||||
}
|
||||
|
||||
void *allocElem();
|
||||
void freeElem(unsigned char *ptr);
|
||||
bool contains(unsigned char *ptr) const;
|
||||
};
|
||||
|
||||
Block *_AllocBlock(unsigned int elemCount, unsigned int elemSize);
|
||||
void _FreeBlock(Block *blk);
|
||||
Block *_AppendBlock(unsigned int elemSize);
|
||||
Block *_GetFreeBlock(unsigned int elemSize); // NULL if none free
|
||||
|
||||
Block *_FindBlockContainingPtr(void *ptr);
|
||||
|
||||
inline unsigned int GetIndexForElemSize(unsigned int elemSize)
|
||||
{
|
||||
//ASSERT(elemSize);
|
||||
//ASSERT(elemSize <= _blockSizeMax);
|
||||
return ((elemSize + (_blockSizeIncr - 1)) / _blockSizeIncr) - 1;
|
||||
}
|
||||
|
||||
static bool Block_ptr_cmp(const Block *blk, const void *ptr);
|
||||
|
||||
Block **_blocks;
|
||||
std::vector<Block*> _allblocks; // always sorted by pointer address
|
||||
|
||||
unsigned int _blockSizeMin;
|
||||
unsigned int _blockSizeMax;
|
||||
unsigned int _blockSizeIncr;
|
||||
|
||||
unsigned int _elemsPerBlockMin;
|
||||
unsigned int _elemsPerBlockMax;
|
||||
};
|
||||
|
||||
#endif
|
84
BBGE/bithacks.h
Normal file
84
BBGE/bithacks.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
#ifndef BITHACKS_H
|
||||
#define BITHACKS_H
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace bithacks {
|
||||
|
||||
typedef unsigned int uint32;
|
||||
typedef int int32;
|
||||
|
||||
// floor to next power of 2
|
||||
inline uint32 flp2(uint32 x)
|
||||
{
|
||||
x |= (x >> 1);
|
||||
x |= (x >> 2);
|
||||
x |= (x >> 4);
|
||||
x |= (x >> 8);
|
||||
x |= (x >> 16);
|
||||
return x - (x >> 1);
|
||||
}
|
||||
|
||||
// ceil to next power of 2
|
||||
inline uint32 clp2(uint32 x)
|
||||
{
|
||||
--x;
|
||||
x |= (x >> 1);
|
||||
x |= (x >> 2);
|
||||
x |= (x >> 4);
|
||||
x |= (x >> 8);
|
||||
x |= (x >> 16);
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
inline uint32 popcnt(uint32 x)
|
||||
{
|
||||
x -= ((x >> 1) & 0x55555555);
|
||||
x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
|
||||
x = (((x >> 4) + x) & 0x0f0f0f0f);
|
||||
x += (x >> 8);
|
||||
x += (x >> 16);
|
||||
return x & 0x0000003f;
|
||||
}
|
||||
|
||||
inline uint32 ctz(uint32 x)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
return __builtin_ctz(x);
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86)
|
||||
unsigned long r = 0;
|
||||
_BitScanForward(&r, x);
|
||||
return r;
|
||||
#else
|
||||
return popcnt((x & -x) - 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline unsigned int clz(uint32 x)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
return __builtin_clz(x);
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86)
|
||||
unsigned long r = 0;
|
||||
_BitScanReverse(&r, x);
|
||||
return r;
|
||||
#else
|
||||
x |= (x >> 1);
|
||||
x |= (x >> 2);
|
||||
x |= (x >> 4);
|
||||
x |= (x >> 8);
|
||||
x |= (x >> 16);
|
||||
return 32 - popcnt(x);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}; // end namespace bithacks
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue