From 827dbd8cf307d51b745c940c06dc529636b4a437 Mon Sep 17 00:00:00 2001 From: csoler Date: Sun, 20 Feb 2011 21:36:21 +0000 Subject: [PATCH] added a memory management class for small objects. git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@4046 b45a01b8-16f6-495d-af2f-9b41ad6348cc --- libretroshare/src/ft/fttransfermodule.cc | 1 - libretroshare/src/libretroshare.pro | 2 + libretroshare/src/serialiser/rsserial.cc | 65 ++++ libretroshare/src/serialiser/rsserial.h | 8 +- libretroshare/src/tests/general/Makefile | 7 +- .../tests/general/memory_management_test.cc | 61 ++++ libretroshare/src/util/smallobject.cc | 285 ++++++++++++++++++ libretroshare/src/util/smallobject.h | 113 +++++++ 8 files changed, 538 insertions(+), 4 deletions(-) create mode 100644 libretroshare/src/tests/general/memory_management_test.cc create mode 100644 libretroshare/src/util/smallobject.cc create mode 100644 libretroshare/src/util/smallobject.h diff --git a/libretroshare/src/ft/fttransfermodule.cc b/libretroshare/src/ft/fttransfermodule.cc index 2b087a067..315d41287 100644 --- a/libretroshare/src/ft/fttransfermodule.cc +++ b/libretroshare/src/ft/fttransfermodule.cc @@ -26,7 +26,6 @@ /****** * #define FT_DEBUG 1 *****/ -#define FT_DEBUG 1 #include "retroshare/rsturtle.h" #include "fttransfermodule.h" diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 5c6b41286..50e7d46d3 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -405,6 +405,7 @@ HEADERS += upnp/upnphandler.h HEADERS += util/folderiterator.h \ util/rsdebug.h \ + util/smallobject.h \ util/rsdir.h \ util/rsdiscspace.h \ util/rsnet.h \ @@ -525,6 +526,7 @@ SOURCES += upnp/upnphandler.cc SOURCES += util/folderiterator.cc \ util/rsdebug.cc \ + util/smallobject.cc \ util/rsdir.cc \ util/rsdiscspace.cc \ util/rsnet.cc \ diff --git a/libretroshare/src/serialiser/rsserial.cc b/libretroshare/src/serialiser/rsserial.cc index 6d0e2fe9a..063b46741 100644 --- a/libretroshare/src/serialiser/rsserial.cc +++ b/libretroshare/src/serialiser/rsserial.cc @@ -26,8 +26,11 @@ #include "serialiser/rsbaseserial.h" #include "serialiser/rsserial.h" +#include "util/rsthreads.h" +#include #include +#include #include /*** @@ -44,6 +47,68 @@ RsItem::RsItem(uint32_t t) return; } +#ifdef DO_STATISTICS +class Counter +{ + public: + Counter(int i): _i(i) {} + Counter(): _i(0) {} + + int v() const { return _i ; } + int& v() { return _i ; } + private: + int _i ; +}; + + static RsMutex smtx ; + static std::map size_hits ; + static int nb_rsitem_creations = 0 ; + static int total_rsitem_mallocs = 0 ; + static int total_rsitem_frees = 0 ; + static int total_rsitem_freed = 0 ; + static time_t last_time = 0 ; + +void *RsItem::operator new(size_t s) +{ +// std::cerr << "New RsItem: s=" << s << std::endl; + + RsStackMutex m(smtx) ; + + ++size_hits[ s ].v() ; + + time_t now = time(NULL); + ++nb_rsitem_creations ; + total_rsitem_mallocs += s ; + + if(last_time + 20 < now) + { + std::cerr << "Memory statistics:" << std::endl; + std::cerr << " Total RsItem memory: " << total_rsitem_mallocs << std::endl; + std::cerr << " Total RsItem creations: " << nb_rsitem_creations << std::endl; + std::cerr << " Total RsItem freed memory: " << total_rsitem_freed << std::endl; + std::cerr << " Total RsItem deletions: " << total_rsitem_frees << std::endl; + std::cerr << "Now printing histogram:" << std::endl; + + for(std::map::const_iterator it(size_hits.begin());it!=size_hits.end();++it) + std::cerr << it->first << " " << it->second.v() << std::endl; + last_time = now ; + } + + RsItem *a = static_cast(::operator new(s)) ; + return a ; +} +void RsItem::operator delete(void *p,size_t s) +{ +// std::cerr << "Delete RsItem: s=" << s << std::endl; + + RsStackMutex m(smtx) ; + total_rsitem_freed += s ; + ++total_rsitem_frees ; + + ::operator delete(p) ; +} +#endif + RsItem::RsItem(uint8_t ver, uint8_t cls, uint8_t t, uint8_t subtype) { type = (ver << 24) + (cls << 16) + (t << 8) + subtype; diff --git a/libretroshare/src/serialiser/rsserial.h b/libretroshare/src/serialiser/rsserial.h index cb0fe2881..05ca80eb8 100644 --- a/libretroshare/src/serialiser/rsserial.h +++ b/libretroshare/src/serialiser/rsserial.h @@ -56,6 +56,8 @@ * 8 bits: SubType ******************************************************************/ +#include + const uint8_t RS_PKT_VERSION1 = 0x01; const uint8_t RS_PKT_VERSION_SERVICE = 0x02; @@ -65,11 +67,15 @@ const uint8_t RS_PKT_CLASS_CONFIG = 0x02; const uint8_t RS_PKT_SUBTYPE_DEFAULT = 0x01; /* if only one subtype */ -class RsItem +class RsItem: public RsMemoryManagement::SmallObject { public: RsItem(uint32_t t); RsItem(uint8_t ver, uint8_t cls, uint8_t t, uint8_t subtype); +#ifdef DO_STATISTICS + void *operator new(size_t s) ; + void operator delete(void *,size_t s) ; +#endif virtual ~RsItem(); virtual void clear() = 0; diff --git a/libretroshare/src/tests/general/Makefile b/libretroshare/src/tests/general/Makefile index b6abff391..fed684583 100644 --- a/libretroshare/src/tests/general/Makefile +++ b/libretroshare/src/tests/general/Makefile @@ -7,8 +7,8 @@ RS_TOP_DIR = ../.. include $(RS_TOP_DIR)/tests/scripts/config.mk ############################################################### -TESTOBJ = netsetup_test.o random_test.o -TESTS = netsetup_test random_test +TESTOBJ = netsetup_test.o random_test.o memory_management_test.o +TESTS = netsetup_test random_test memory_management_test all: tests @@ -18,6 +18,9 @@ netsetup_test: netsetup_test.o random_test: random_test.o $(CC) $(CFLAGS) -o random_test random_test.o $(LIBS) +memory_management_test: memory_management_test.o + $(CC) $(CFLAGS) -o memory_management_test memory_management_test.o $(LIBS) + ############################################################### include $(RS_TOP_DIR)/tests/scripts/rules.mk ############################################################### diff --git a/libretroshare/src/tests/general/memory_management_test.cc b/libretroshare/src/tests/general/memory_management_test.cc new file mode 100644 index 000000000..2b680a293 --- /dev/null +++ b/libretroshare/src/tests/general/memory_management_test.cc @@ -0,0 +1,61 @@ +#ifdef LINUX +#include +#endif +#include +#include +#include +#include +#include "util/utest.h" +#include "serialiser/rsserial.h" + +INITTEST(); + +// Make a fake class of size n. +// +template class RsTestItem: public RsItem +{ + public: + unsigned char buff[n] ; + + RsTestItem(uint32_t s) : RsItem(s) {} + virtual void clear() {} + virtual std::ostream& print(std::ostream& o, uint16_t) { return o ; } +}; + +int main(int argc, char **argv) +{ +#ifdef LINUX + feenableexcept(FE_INVALID) ; + feenableexcept(FE_DIVBYZERO) ; +#endif + typedef RsTestItem<17> Test17 ; + typedef RsTestItem<31> Test31 ; + + std::vector v; + + for(int i=0;i<300;++i) + v.push_back(new Test17(0)) ; + for(int i=0;i<700;++i) + v.push_back(new Test31(0)) ; + + RsMemoryManagement::printStatistics() ; + + // Now delete objects randomly. + // + for(int i=0;i<1000;++i) + { + int indx = lrand48()%(int)v.size() ; + + delete v[indx] ; + v[indx] = v.back() ; + v.pop_back() ; + } + + std::cerr << "After memory free: " << std::endl; + + RsMemoryManagement::printStatistics() ; + + FINALREPORT("memory_management_test"); + exit(TESTRESULT()); +} + diff --git a/libretroshare/src/util/smallobject.cc b/libretroshare/src/util/smallobject.cc new file mode 100644 index 000000000..0f312a0a2 --- /dev/null +++ b/libretroshare/src/util/smallobject.cc @@ -0,0 +1,285 @@ +#include +#include "smallobject.h" +#include "util/rsthreads.h" + +using namespace RsMemoryManagement ; + +RsMutex SmallObject::_mtx ; +SmallObjectAllocator SmallObject::_allocator(RsMemoryManagement::MAX_SMALL_OBJECT_SIZE) ; + +void Chunk::init(size_t blockSize,unsigned char blocks) +{ + _data = new unsigned char[blockSize*blocks] ; + _firstAvailableBlock = 0 ; + _blocksAvailable = blocks ; + + // Inits the first byte of each block to point to the next available block + // + unsigned char *p = _data ; + + for(unsigned char i=0;i= 2) ; + + unsigned char *result = _data + _firstAvailableBlock*blockSize ; + + // Grab id of next available block from the block being allocated. + + // Always the case because the first available block is the first, so the next + // available block is given by *result. + // + _firstAvailableBlock = *result ; + --_blocksAvailable ; + + return result ; +} + +void Chunk::deallocate(void *p,size_t blockSize) +{ + assert(p >= _data) ; + + unsigned char *toRelease = static_cast(p) ; + + // alignment check + + assert( (toRelease - _data) % blockSize == 0 ) ; + + *toRelease = _firstAvailableBlock ; + _firstAvailableBlock = static_cast( (toRelease - _data)/blockSize) ; + + // truncation check + + assert(_firstAvailableBlock == (toRelease - _data)/blockSize); + + ++_blocksAvailable ; +} + +void Chunk::printStatistics(int blockSize) const +{ + std::cerr << " blocksAvailable : " << (int)_blocksAvailable << std::endl; + std::cerr << " firstBlockAvailable: " << (int)_firstAvailableBlock << std::endl; + std::cerr << " blocks : " << (void*)_data << " to " << (void*)(_data+BLOCKS_PER_CHUNK*blockSize) << std::endl; +} + +FixedAllocator::FixedAllocator(size_t bytes) +{ + _blockSize = bytes ; + _numBlocks = BLOCKS_PER_CHUNK ; + _allocChunk = -1 ; + _deallocChunk = -1 ; +} +FixedAllocator::~FixedAllocator() +{ + for(uint32_t i=0;i<_chunks.size();++i) + { + _chunks[i]->free() ; + delete _chunks[i] ; + } +} + +void *FixedAllocator::allocate() +{ + if(_allocChunk < 0 || _chunks[_allocChunk]->_blocksAvailable == 0) + { + // find availabel memory in this chunk + // + uint32_t i ; + _allocChunk = -1 ; + for(i=0;i<_chunks.size();++i) + if(_chunks[i]->_blocksAvailable > 0) // found a chunk + { + _allocChunk = i ; + break ; + } + if( _allocChunk < 0 ) + { + _chunks.reserve(_chunks.size()+1) ; + Chunk *newChunk = new Chunk; + + if(newChunk == NULL) + { + std::cerr << "RsMemoryManagement: ran out of memory !" << std::endl; + exit(-1) ; + } + newChunk->init(_blockSize,_numBlocks) ; + _chunks.push_back(newChunk) ; + + _allocChunk = _chunks.size()-1 ; + _deallocChunk = _chunks.size()-1 ; + } + + } + assert(_chunks[_allocChunk] != NULL) ; + assert(_chunks[_allocChunk]->_blocksAvailable > 0) ; + + return _chunks[_allocChunk]->allocate(_blockSize) ; +} +void FixedAllocator::deallocate(void *p) +{ + if(_deallocChunk < 0 || !chunkOwnsPointer(*_chunks[_deallocChunk],p)) + { + // find the chunk that contains this pointer. Perform a linear search. + + _deallocChunk = -1 ; + + for(uint32_t i=0;i<_chunks.size();++i) + if(chunkOwnsPointer(*_chunks[i],p)) + { + _deallocChunk = i ; + break ; + } + } + assert(_chunks[_deallocChunk] != NULL) ; + + _chunks[_deallocChunk]->deallocate(p,_blockSize) ; + + if(_chunks[_deallocChunk]->_blocksAvailable == BLOCKS_PER_CHUNK) + { + _chunks[_deallocChunk]->free() ; + delete _chunks[_deallocChunk] ; + + _chunks[_deallocChunk] = _chunks.back() ; + if(_allocChunk == _deallocChunk) _allocChunk = -1 ; + if(_allocChunk == ((int)_chunks.size())-1) _allocChunk = _deallocChunk ; + _deallocChunk = -1 ; + _chunks.pop_back(); + } +} +void FixedAllocator::printStatistics() const +{ + std::cerr << " numBLocks=" << (int)_numBlocks << std::endl; + std::cerr << " blockSize=" << (int)_blockSize << std::endl; + std::cerr << " Number of chunks: " << _chunks.size() << std::endl; + for(uint32_t i=0;i<_chunks.size();++i) + _chunks[i]->printStatistics(_blockSize) ; +} + +SmallObjectAllocator::SmallObjectAllocator(size_t maxObjectSize) + : _maxObjectSize(maxObjectSize) +{ + _lastAlloc = NULL ; + _lastDealloc = NULL ; +} + +SmallObjectAllocator::~SmallObjectAllocator() +{ + for(std::map::const_iterator it(_pool.begin());it!=_pool.end();++it) + delete it->second ; +} + +void *SmallObjectAllocator::allocate(size_t bytes) +{ + if(bytes > _maxObjectSize) + return malloc(bytes) ; + else if(_lastAlloc != NULL && _lastAlloc->blockSize() == bytes) + return _lastAlloc->allocate() ; + else + { + std::map::iterator it(_pool.find(bytes)) ; + + if(it == _pool.end()) + { + _pool[bytes] = new FixedAllocator(bytes) ; + it = _pool.find(bytes) ; + } + _lastAlloc = it->second ; + + return it->second->allocate() ; + } +} + +void SmallObjectAllocator::deallocate(void *p,size_t bytes) +{ + if(bytes > _maxObjectSize) + free(p) ; + else if(_lastDealloc != NULL && _lastDealloc->blockSize() == bytes) + _lastDealloc->deallocate(p) ; + else + { + std::map::iterator it(_pool.find(bytes)) ; + + if(it == _pool.end()) + { + _pool[bytes] = new FixedAllocator(bytes) ; + it = _pool.find(bytes) ; + } + it->second->deallocate(p) ; + _lastDealloc = it->second ; + } +} + +void SmallObjectAllocator::printStatistics() const +{ + std::cerr << "RsMemoryManagement Statistics:" << std::endl; + std::cerr << " Total Fixed-size allocators: " << _pool.size() << std::endl; + std::cerr << "Pool" << std::endl; + + for(std::map::const_iterator it(_pool.begin());it!=_pool.end();++it) + { + std::cerr << " Allocator for size " << it->first << " : " << std::endl; + std::cerr << " Last Alloc: " << _lastAlloc << std::endl; + std::cerr << " Last Dealloc: " << _lastDealloc << std::endl; + it->second->printStatistics() ; + } +} + +void *SmallObject::operator new(size_t size) +{ +#ifdef DEBUG_MEMORY + bool print=false ; + { + RsStackMutex m(_mtx) ; + static time_t last_time = 0 ; + time_t now = time(NULL) ; + if(now > last_time + 20) + { + last_time = now ; + print=true ; + } + } + if(print) + printStatistics() ; +#endif + + RsStackMutex m(_mtx) ; + void *p = _allocator.allocate(size) ; +#ifdef DEBUG_MEMORY + std::cerr << "new RsItem: " << p << ", size=" << size << std::endl; +#endif + return p ; +} + +void SmallObject::operator delete(void *p,size_t size) +{ + RsStackMutex m(_mtx) ; + _allocator.deallocate(p,size) ; +#ifdef DEBUG_MEMORY + std::cerr << "del RsItem: " << p << ", size=" << size << std::endl; +#endif +} + +void SmallObject::printStatistics() +{ + RsStackMutex m(_mtx) ; + _allocator.printStatistics() ; +} + +void RsMemoryManagement::printStatistics() +{ + SmallObject::printStatistics(); +} + + diff --git a/libretroshare/src/util/smallobject.h b/libretroshare/src/util/smallobject.h new file mode 100644 index 000000000..030542ac4 --- /dev/null +++ b/libretroshare/src/util/smallobject.h @@ -0,0 +1,113 @@ +/* + * libretroshare/src/util: smallobject.h + * + * Universal Networking Header for RetroShare. + * + * Copyright 2011 by Cyril Soler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License Version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + * + * Please report all bugs and problems to "cyril.soler@users.sourceforge.net". + * + */ + +#include +#include + +#include +#include + +#include + +namespace RsMemoryManagement +{ + static const int MAX_SMALL_OBJECT_SIZE = 128 ; + static const unsigned char BLOCKS_PER_CHUNK = 255 ; + + struct Chunk + { + void init(size_t blockSize,unsigned char blocks); + void free() ; + + void *allocate(size_t); + void deallocate(void *p,size_t blockSize); + + unsigned char *_data ; + unsigned char _firstAvailableBlock ; + unsigned char _blocksAvailable ; + + void printStatistics(int blockSize) const ; + }; + + class FixedAllocator + { + public: + FixedAllocator(size_t bytes) ; + virtual ~FixedAllocator() ; + + void *allocate(); + void deallocate(void *p) ; + inline size_t blockSize() const { return _blockSize ; } + + inline bool chunkOwnsPointer(const Chunk& c,void *p) const + { + return p >= c._data && (static_cast(p)-c._data)/_blockSize < _numBlocks ; + } + + void printStatistics() const ; + private: + size_t _blockSize ; + unsigned char _numBlocks ; + std::vector _chunks ; + int _allocChunk ; // last chunk that provided allocation. -1 if not inited + int _deallocChunk ; // last chunk that provided de-allocation. -1 if not inited + }; + + class SmallObjectAllocator + { + public: + SmallObjectAllocator(size_t maxObjectSize) ; + virtual ~SmallObjectAllocator() ; + + void *allocate(size_t numBytes) ; + void deallocate(void *p,size_t size) ; + + void printStatistics() const ; + private: + std::map _pool ; + FixedAllocator *_lastAlloc ; + FixedAllocator *_lastDealloc ; + size_t _maxObjectSize ; + }; + + class SmallObject + { + public: + static void *operator new(size_t size) ; + static void operator delete(void *p,size_t size) ; + + static void printStatistics() ; + + virtual ~SmallObject() {} + + private: + static SmallObjectAllocator _allocator ; + static RsMutex _mtx; + }; + + extern void printStatistics() ; +} + +