diff --git a/build/cmake/CMakeLists.txt b/build/cmake/CMakeLists.txt index 23831fac1..eccc7a8bf 100755 --- a/build/cmake/CMakeLists.txt +++ b/build/cmake/CMakeLists.txt @@ -1400,6 +1400,7 @@ SET(SIRIKATA_CORE_SOURCES ${TRACE_PBJ_CPP_FILES} ${LIBCORE_SOURCE_DIR}/jpeg-arhc/Scan.cpp ${LIBCORE_SOURCE_DIR}/jpeg-arhc/BumpAllocator.cpp + ${LIBCORE_SOURCE_DIR}/jpeg-arhc/MemMgrAllocator.cpp ${LIBCORE_SOURCE_DIR}/jpeg-arhc/Decoder.cpp ${LIBCORE_SOURCE_DIR}/jpeg-arhc/Compression.cpp ${LIBCORE_SOURCE_DIR}/jpeg-arhc/Zlib0.cpp @@ -1977,6 +1978,7 @@ SET(BENCH_SOURCES #test source files SET(CXXTESTSources +${TEST_LIBCORE_SOURCE_DIR}/MemMgrAllocatorTest.hpp ${TEST_LIBCORE_SOURCE_DIR}/LosslessJpegTest.hpp ${TEST_LIBCORE_SOURCE_DIR}/MuxReadWriterTest.hpp ${TEST_LIBCORE_SOURCE_DIR}/CompressionTest.hpp diff --git a/libcore/include/sirikata/core/jpeg-arhc/MemMgrAllocator.hpp b/libcore/include/sirikata/core/jpeg-arhc/MemMgrAllocator.hpp new file mode 100644 index 000000000..244a752d8 --- /dev/null +++ b/libcore/include/sirikata/core/jpeg-arhc/MemMgrAllocator.hpp @@ -0,0 +1,70 @@ +//---------------------------------------------------------------- +// Statically-allocated memory manager +// +// by Eli Bendersky (eliben@gmail.com) +// +// This code is in the public domain. + +/* Sirikata Memory Management system + * + * + * Copyright (c) 2015 Eli Bendersky, Daniel Reiter Horn + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Sirikata { + +// Initialize the memory manager. This function should be called +// exactly once per thread that wishes to allocate memory +// +SIRIKATA_FUNCTION_EXPORT void memmgr_init(size_t size, size_t min_pool_alloc_quantas = 256); + +// Uninitialize the memory manager. This function should be called +// exactly once per thread that exits +SIRIKATA_FUNCTION_EXPORT void memmgr_destroy(); + +// 'malloc' clone +// +SIRIKATA_FUNCTION_EXPORT void* memmgr_alloc(size_t nbytes); + +// 'free' clone +// +SIRIKATA_FUNCTION_EXPORT void memmgr_free(void* ap); + +// Prints statistics about the current state of the memory +// manager +// +SIRIKATA_FUNCTION_EXPORT void memmgr_print_stats(); + +} +namespace Sirikata { +SIRIKATA_FUNCTION_EXPORT void *MemMgrAllocatorMalloc(void *opaque, size_t nmemb, size_t size); +SIRIKATA_FUNCTION_EXPORT void MemMgrAllocatorFree (void *opaque, void *ptr); +SIRIKATA_FUNCTION_EXPORT void * MemMgrAllocatorInit(size_t prealloc_size, unsigned char alignment); +SIRIKATA_FUNCTION_EXPORT void MemMgrAllocatorDestroy(void *opaque); +SIRIKATA_FUNCTION_EXPORT void* MemMgrAllocatorRealloc(void * ptr, size_t size, size_t *actualSize, unsigned int movable, void *opaque); +SIRIKATA_FUNCTION_EXPORT size_t MemMgrAllocatorMsize(void * ptr, void *opaque); + +} diff --git a/libcore/include/sirikata/core/util/ArrayNd.hpp b/libcore/include/sirikata/core/util/ArrayNd.hpp index af6b13eb0..489040fb6 100644 --- a/libcore/include/sirikata/core/util/ArrayNd.hpp +++ b/libcore/include/sirikata/core/util/ArrayNd.hpp @@ -161,7 +161,7 @@ template ::Slice slice(const StartEnd&range) const { return slice(); } - template typename Array1d typename Array1d::Slice slice() { uint8_t assert_slice_legal[kend > s0 ? -1 : 1]; uint8_t assert_slice_start_legal[kend < kstart ? -1 : 1]; diff --git a/libcore/src/jpeg-arhc/MemMgrAllocator.cpp b/libcore/src/jpeg-arhc/MemMgrAllocator.cpp new file mode 100644 index 000000000..e0d7a98d6 --- /dev/null +++ b/libcore/src/jpeg-arhc/MemMgrAllocator.cpp @@ -0,0 +1,373 @@ +//---------------------------------------------------------------- +// Statically-allocated memory manager +// +// by Eli Bendersky (eliben@gmail.com) +// +// This code is in the public domain. + +/* Sirikata Memory Management system + * + * + * Copyright (c) 2015 Eli Bendersky, Daniel Reiter Horn + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#if __cplusplus <= 199711L +#define thread_local __thread +#endif + +namespace Sirikata { +union mem_header_union +{ + typedef char Align[16]; + struct + { + // Pointer to the next block in the free list + // + union mem_header_union* next; + + // Size of the block (in quantas of sizeof(mem_header_t)) + // + size_t size; + } s; + + // Used to align headers in memory to a boundary + // + Align align_dummy; +}; + +typedef union mem_header_union mem_header_t; + +struct MemMgrState { + size_t min_pool_alloc_quantas; + size_t pool_size; +#ifdef MEMMGR_POOL_SIZE + uint8_t pool[MEMMGR_POOL_SIZE]; +#else + uint8_t *pool; +#endif + mem_header_t base; +// Start of free list +// + mem_header_t* freep; +// Initial empty list +// + size_t pool_free_pos; +// Static pool for new allocations +// +}; +thread_local MemMgrState memmgr = { + 256 +#ifdef MEMMGR_POOL_SIZE + ,MEMMGR_POOL_SIZE + ,{} +#else + ,0 + ,0 +#endif + ,{{0,0}} + ,0 + ,0 +}; + + +void memmgr_destroy() { +#ifndef MEMMGR_POOL_SIZE + munmap(memmgr.pool, memmgr.pool_size); +#endif + memset(&memmgr, 0, sizeof(MemMgrState)); +} +void memmgr_init(size_t pool_size, size_t min_pool_alloc_quantas) +{ + memset(&memmgr, 0, sizeof(MemMgrState)); + memmgr.base.s.next = 0; + memmgr.base.s.size = 0; + memmgr.freep = 0; + memmgr.pool_free_pos = 0; +#ifdef MEMMGR_POOL_SIZE + memmgr.pool_size = MEMMGR_POOL_SIZE; +#else + memmgr.pool = (uint8_t*)mmap(NULL, pool_size, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON, -1, 0); + + memmgr.pool_size = pool_size; +#endif +} + + +void memmgr_print_stats() +{ + #ifdef DEBUG_MEMMGR_SUPPORT_STATS + mem_header_t* p; + + printf("------ Memory manager stats ------\n\n"); + printf( "Memmgr.Pool: free_pos = %lu (%lu uint8_ts left)\n\n", + memmgr.pool_free_pos, memmgr.pool_size - memmgr.pool_free_pos); + + p = (mem_header_t*) memmgr.pool; + + while (p < (mem_header_t*) (memmgr.pool + memmgr.pool_free_pos)) + { + printf( " * Addr: 0x%8p; Size: %8lu\n", + p, p->s.size); + + p += p->s.size; + } + + printf("\nFree list:\n\n"); + + if (memmgr.freep) + { + p = memmgr.freep; + + while (1) + { + printf( " * Addr: 0x%8p; Size: %8lu; Next: 0x%8p\n", + p, p->s.size, p->s.next); + + p = p->s.next; + + if (p == memmgr.freep) + break; + } + } + else + { + printf("Empty\n"); + } + + printf("\n"); + #endif // DEBUG_MEMMGR_SUPPORT_STATS +} + + +static mem_header_t* get_mem_from_pool(size_t nquantas) +{ + size_t total_req_size; + + mem_header_t* h; + + if (nquantas < memmgr.min_pool_alloc_quantas) + nquantas = memmgr.min_pool_alloc_quantas; + + total_req_size = nquantas * sizeof(mem_header_t); + + if (memmgr.pool_free_pos + total_req_size <= memmgr.pool_size) + { + h = (mem_header_t*) (memmgr.pool + memmgr.pool_free_pos); + h->s.size = nquantas; + memmgr_free((void*) (h + 1)); + memmgr.pool_free_pos += total_req_size; + } + else + { + return 0; + } + + return memmgr.freep; +} + + +// Allocations are done in 'quantas' of header size. +// The search for a free block of adequate size begins at the point 'memmgr.freep' +// where the last block was found. +// If a too-big block is found, it is split and the tail is returned (this +// way the header of the original needs only to have its size adjusted). +// The pointer returned to the user points to the free space within the block, +// which begins one quanta after the header. +// +void* memmgr_alloc(size_t nuint8_ts) +{ + mem_header_t* p; + mem_header_t* prevp; + + // Calculate how many quantas are required: we need enough to house all + // the requested uint8_ts, plus the header. The -1 and +1 are there to make sure + // that if nuint8_ts is a multiple of nquantas, we don't allocate too much + // + size_t nquantas = (nuint8_ts + sizeof(mem_header_t) - 1) / sizeof(mem_header_t) + 1; + + // First alloc call, and no free list yet ? Use 'base' for an initial + // degenerate block of size 0, which points to itself + // + if ((prevp = memmgr.freep) == 0) + { + memmgr.base.s.next = memmgr.freep = prevp = &memmgr.base; + memmgr.base.s.size = 0; + } + + for (p = prevp->s.next; ; prevp = p, p = p->s.next) + { + // big enough ? + if (p->s.size >= nquantas) + { + // exactly ? + if (p->s.size == nquantas) + { + // just eliminate this block from the free list by pointing + // its prev's next to its next + // + prevp->s.next = p->s.next; + } + else // too big + { + p->s.size -= nquantas; + p += p->s.size; + p->s.size = nquantas; + } + + memmgr.freep = prevp; + return (void*) (p + 1); + } + // Reached end of free list ? + // Try to allocate the block from the memmgr.pool. If that succeeds, + // get_mem_from_pool adds the new block to the free list and + // it will be found in the following iterations. If the call + // to get_mem_from_pool doesn't succeed, we've run out of + // memory + // + else if (p == memmgr.freep) + { + if ((p = get_mem_from_pool(nquantas)) == 0) + { + #ifdef DEBUG_MEMMGR_FATAL + printf("!! Memory allocation failed !!\n"); + #endif + return 0; + } + } + } +} + + +// Scans the free list, starting at memmgr.freep, looking the the place to insert the +// free block. This is either between two existing blocks or at the end of the +// list. In any case, if the block being freed is adjacent to either neighbor, +// the adjacent blocks are combined. +// +void memmgr_free(void* ap) +{ + if ((uint8_t*)ap >= memmgr.pool + memmgr.pool_size + || (uint8_t*)ap < memmgr.pool) { + // illegal address or on another thread. +#ifdef DEBUG_MEMMGR_FATAL + fprintf(stderr, "Memory freed on another thread than it was allocated on\n"); +#endif + return; + } + mem_header_t* block; + mem_header_t* p; + + // acquire pointer to block header + block = ((mem_header_t*) ap) - 1; + + // Find the correct place to place the block in (the free list is sorted by + // address, increasing order) + // + for (p = memmgr.freep; !(block > p && block < p->s.next); p = p->s.next) + { + // Since the free list is circular, there is one link where a + // higher-addressed block points to a lower-addressed block. + // This condition checks if the block should be actually + // inserted between them + // + if (p >= p->s.next && (block > p || block < p->s.next)) + break; + } + + // Try to combine with the higher neighbor + // + if (block + block->s.size == p->s.next) + { + block->s.size += p->s.next->s.size; + block->s.next = p->s.next->s.next; + } + else + { + block->s.next = p->s.next; + } + + // Try to combine with the lower neighbor + // + if (p + p->s.size == block) + { + p->s.size += block->s.size; + p->s.next = block->s.next; + } + else + { + p->s.next = block; + } + + memmgr.freep = p; +} + + + +void *MemMgrAllocatorMalloc(void *opaque, size_t nmemb, size_t size) { + return memmgr_alloc(nmemb * size); +} +void MemMgrAllocatorFree (void *opaque, void *ptr) { + memmgr_free(ptr); +} +void * MemMgrAllocatorInit(size_t prealloc_size, unsigned char alignment) { + assert(alignment == sizeof(mem_header_union::Align)); + memmgr_init(prealloc_size, 256); + return memmgr_alloc(1); +} +void MemMgrAllocatorDestroy(void *opaque) { + memmgr_free(opaque); + memmgr_destroy(); +} +size_t MemMgrAllocatorMsize(void * ptr, void *opaque) { + mem_header_t* block = ((mem_header_t*) ptr) - 1; + return block->s.size * sizeof(mem_header_t); +} +void *MemMgrAllocatorRealloc(void * ptr, size_t amount, size_t *ret_size, unsigned int movable, void *opaque) { + if (amount == 0) { + memmgr_free(ptr); + return NULL; + } + size_t ptr_actual_size = 0; + if (ptr) { + ptr_actual_size = MemMgrAllocatorMsize(ptr, opaque); + if (ptr_actual_size >= amount) { + *ret_size = ptr_actual_size; + return ptr; + } + if (!movable) { + return NULL; + } + } + void * retval = memmgr_alloc(amount); + *ret_size = MemMgrAllocatorMsize(retval, opaque); + if (ptr) { + memcpy(retval, ptr, std::min(amount, ptr_actual_size)); + memmgr_free(ptr); + } + return retval; +} + +} diff --git a/test/unit/libcore/MemMgrAllocatorTest.hpp b/test/unit/libcore/MemMgrAllocatorTest.hpp new file mode 100644 index 000000000..0c8bec7ea --- /dev/null +++ b/test/unit/libcore/MemMgrAllocatorTest.hpp @@ -0,0 +1,201 @@ +/* Sirikata Jpeg Texture Transfer + * MemMgrAllocator Unit Tests + * + * Copyright (c) 2015, Daniel Reiter Horn + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#if __cplusplus <= 199711L +#include +#else +#include +#endif +class MemMgrAllocatorTest : public CxxTest::TestSuite +{ + void * wrapped_alloc(size_t size) { + using namespace Sirikata; + TS_ASSERT_EQUALS((size & (sizeof(void*) - 1)), 0); + void ** retval = (void**)memmgr_alloc(size); + size/=sizeof(void*); + for (size_t i= 0; i < size;++i) { + retval[i] = retval; + } + return retval; + } + void wrapped_free(void * data, size_t size) { + using namespace Sirikata; + void ** test = (void**)data; + size /= sizeof(void*); + for (size_t i = 0; i < size; ++i) { + TS_ASSERT_EQUALS(test[i], data); // make sure data hasn't been corrutped + } + memmgr_free(data); + } + int integration() { + using namespace Sirikata; + std::map *allocated = NULL; + memmgr_init(128 * 1024 * 1024); + allocated = new std::map; + srand(10420); + for (size_t i = 0; i < 16384; ++i) { + size_t to_allocate = (rand() % 256 + 1) * sizeof(void*); + (*allocated)[(char*)wrapped_alloc(to_allocate)] = to_allocate; + if ((i & 3) == 3) { + if ((*allocated).size()) { + char* lowest = allocated->begin()->first; + std::map::iterator last = allocated->end(); + --last; + char * highest = last->first; + size_t delta = highest - lowest; + std::map::iterator mfd = last; + if (delta !=0) { + mfd = allocated->lower_bound(lowest + rand()% delta); + if (mfd == allocated->end()) { + --mfd; + } + } + wrapped_free(mfd->first, mfd->second); + allocated->erase(mfd); + } + } + } + + memmgr_free(allocated); // test what happens if you free something out of bounds + while(!allocated->empty()) { //clear the remainder + wrapped_free(allocated->begin()->first, allocated->begin()->second); + allocated->erase(allocated->begin()); + } + delete allocated; + memmgr_destroy(); + return 0; + } + void unit() { + using namespace Sirikata; + uint8_t *p[30] = {0}; + int i; + memmgr_init(8 * 1024, 16); + // Each header uses 8 bytes, so this allocates + // 3 * (2048 + 8) = 6168 bytes, leaving us + // with 8192 - 6168 = 2024 + // + for (i = 0; i < 3; ++i) + { + p[i] = (uint8_t*)memmgr_alloc(2048); + TS_ASSERT(p[i]); + } + + // Allocate all the remaining memory + // + p[4] = (uint8_t*)memmgr_alloc(sizeof(void*) == 4 ? 2016 : 1984); + TS_ASSERT(p[4]); + + // Nothing left... + // + p[5] = (uint8_t*)memmgr_alloc(1); + TS_ASSERT(p[5] == 0); + + // Release the second block. This frees 2048 + 8 bytes. + // + memmgr_free(p[1]); + p[1] = 0; + + // Now we can allocate several smaller chunks from the + // free list. There, they can be smaller than the + // minimal allocation size. + // Allocations of 100 require 14 quantas (13 for the + // requested space, 1 for the header). So it allocates + // 112 bytes. We have 18 allocations to make: + // + for (i = 10; i < (sizeof(void*) == 4 ? 28 : 26); ++i) + { + p[i] = (byte*)memmgr_alloc(100); + TS_ASSERT(p[i]); + } + + // Not enough for another one... + // + p[28] = (byte*)memmgr_alloc(100); + TS_ASSERT(p[28] == 0); + + // Now free everything + // + for (i = 0; i < 30; ++i) + { + if (p[i]) + memmgr_free(p[i]); + } + + memmgr_print_stats(); + memmgr_destroy(); + } +public: + void testIntegration( void ) { + using namespace Sirikata; +#if __cplusplus <= 199711L + Thread a("A", std::tr1::bind(&MemMgrAllocatorTest::integration, this)); + Thread b("B", std::tr1::bind(&MemMgrAllocatorTest::integration, this)); + Thread c("C", std::tr1::bind(&MemMgrAllocatorTest::integration, this)); + Thread d("D", std::tr1::bind(&MemMgrAllocatorTest::integration, this)); +#else + std::thread a(std::bind(&MemMgrAllocatorTest::integration, this)); + std::thread b(std::bind(&MemMgrAllocatorTest::integration, this)); + std::thread c(std::bind(&MemMgrAllocatorTest::integration, this)); + std::thread d(std::bind(&MemMgrAllocatorTest::integration, this)); +#endif + int retval = integration(); + a.join(); + b.join(); + c.join(); + d.join(); + if (retval == 0) { + printf("OK\n"); + } + + } + void testUnit ( void ) { + using namespace Sirikata; +#if __cplusplus <= 199711L + Thread a("A", std::tr1::bind(&MemMgrAllocatorTest::unit, this)); + Thread b("B", std::tr1::bind(&MemMgrAllocatorTest::unit, this)); + Thread c("C", std::tr1::bind(&MemMgrAllocatorTest::unit, this)); + Thread d("D", std::tr1::bind(&MemMgrAllocatorTest::unit, this)); +#else + std::thread a(std::bind(&MemMgrAllocatorTest::unit, this)); + std::thread b(std::bind(&MemMgrAllocatorTest::unit, this)); + std::thread c(std::bind(&MemMgrAllocatorTest::unit, this)); + std::thread d(std::bind(&MemMgrAllocatorTest::unit, this)); +#endif + unit(); + a.join(); + b.join(); + c.join(); + d.join(); + printf("OK\n"); + } +};