zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit 597c5213fe05ec840f864a44136419dee8a1d2d8
parent 1a7bf04b9f89d2168241b57c6ce7fda87053a7f5
Author: fundamental <mark.d.mccurry@gmail.com>
Date:   Fri, 17 Oct 2014 11:30:01 -0400

Add Multi-Pool Info To Allocator

Diffstat:
Msrc/Misc/Allocator.cpp | 168++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Misc/Allocator.h | 31+++++++++++++++++++------------
Asrc/Tests/AllocatorTest.h | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Tests/CMakeLists.txt | 3+++
4 files changed, 299 insertions(+), 18 deletions(-)

diff --git a/src/Misc/Allocator.cpp b/src/Misc/Allocator.cpp @@ -5,22 +5,178 @@ #include "../../tlsf/tlsf.h" #include "Allocator.h" +//recursive type class to avoid void *v = *(void**)v style casting +struct next_t +{ + next_t *next; + size_t pool_size; +}; + +void *data(next_t *n) +{ + return n+sizeof(next_t); +} + + +struct AllocatorImpl +{ + void *tlsf = 0;; + + //singly linked list of memory pools + //XXX this may violate alignment on some platforms if malloc doesn't return + //nice values + next_t *pools = 0; +}; + Allocator::Allocator(void) { - void *buf = malloc(1024*1024); - impl = tlsf_create_with_pool(buf, 1024*1024); + impl = new AllocatorImpl; + size_t default_size = 5*1024*1024; + impl->pools = (next_t*)malloc(default_size); + impl->pools->next = 0x0; + impl->pools->pool_size = default_size; + size_t off = tlsf_size() + tlsf_pool_overhead() + sizeof(next_t); + //printf("Generated Memory Pool with '%p'\n", impl->pools); + impl->tlsf = tlsf_create_with_pool(((char*)impl->pools)+off, default_size-2*off); + //printf("Allocator(%p)\n", impl); +} + +Allocator::~Allocator(void) +{ + next_t *n = impl->pools; + while(n) { + next_t *nn = n->next; + free(n); + n = nn; + } + delete impl; } void *Allocator::alloc_mem(size_t mem_size) { - void *mem = tlsf_malloc(impl, mem_size); - //fprintf(stderr, "Allocating memory (%p)\n", mem); + void *mem = tlsf_malloc(impl->tlsf, mem_size); + //printf("Allocator.malloc(%p, %d) = %p\n", impl, mem_size, mem); + //void *mem = malloc(mem_size); + //printf("Allocator result = %p\n", mem); return mem; - //return malloc(mem_size); } void Allocator::dealloc_mem(void *memory) { //fprintf(stderr, "Freeing memory (%p)\n", memory); - tlsf_free(impl, memory); + tlsf_free(impl->tlsf, memory); //free(memory); } + +bool Allocator::lowMemory(unsigned n, size_t chunk_size) +{ + //This should stay on the stack + void *buf[n]; + for(unsigned i=0; i<n; ++i) + buf[i] = tlsf_malloc(impl->tlsf, chunk_size); + bool outOfMem = false; + for(unsigned i=0; i<n; ++i) + outOfMem |= (buf[i] == nullptr); + for(unsigned i=0; i<n; ++i) + if(buf[i]) + tlsf_free(impl->tlsf, buf[i]); + + return outOfMem; +} + + +void Allocator::addMemory(void *v, size_t mem_size) +{ + next_t *n = impl->pools; + while(n->next) n = n->next; + n->next = (next_t*)v; + n->next->next = 0x0; + n->next->pool_size = mem_size; + //printf("Inserting '%p'\n", v); + off_t off = sizeof(next_t) + tlsf_pool_overhead(); + void *result = + tlsf_add_pool(impl->tlsf, ((char*)n->next)+off, + //0x0eadbeef); + mem_size-off-sizeof(size_t)); + if(!result) + printf("FAILED TO INSERT MEMORY POOL\n"); +};//{(void)mem_size;}; + +//From tlsf internals +typedef struct block_header_t +{ + /* Points to the previous physical block. */ + struct block_header_t* prev_phys_block; + + /* The size of this block, excluding the block header. */ + size_t size; + + /* Next and previous free blocks. */ + struct block_header_t* next_free; + struct block_header_t* prev_free; +} block_header_t; +static const size_t block_header_free_bit = 1 << 0; + +bool Allocator::memFree(void *pool) +{ + size_t bh_shift = sizeof(next_t)+sizeof(size_t); + //printf("memFree(%p)\n", pool); + bool isFree = true; + block_header_t &bh = *(block_header_t*)((char*)pool+bh_shift); + //printf("size_test = %x\n", 50*1024*1024); + //printf("size1 = %x\n", bh.size); + if((bh.size&block_header_free_bit) == 0) + isFree = false; + //printf("Step 1 = %d\n", isFree); + block_header_t &bhn = *(block_header_t*) + (((char*)&bh)+((bh.size&~0x3)+bh_shift-2*sizeof(size_t))); + //printf("size2 = %x\n", bhn.size); + if((bhn.size&block_header_free_bit) != 0) + isFree = false; + //printf("Step 2 = %d\n", isFree); + if((bhn.size&~0x3) != 0) + isFree = false; + //printf("Step 3 = %d\n", isFree); + //printf("Result = %d\n\n", isFree); + + return isFree; +} + +int Allocator::memPools() +{ + int i = 1; + next_t *n = impl->pools; + while(n->next) { + i++; + n = n->next; + } + return i; +} + +int Allocator::freePools() +{ + int i = 0; + next_t *n = impl->pools->next; + while(n) { + if(memFree(n)) + i++; + n = n->next; + } + return i; +} + +/* + * Notes on tlsf internals + * - TLSF consists of blocks linked by block headers and these form a doubly + * linked list of free segments + * - Original memory is [control_t pool] + * base sentinal + * Pools are [block_t block_t blocks ...] + * Blocks are [memory block_t](??) + * - These are stored in the control_t structure in an order dependent on the + * size that they are + * it's a bit unclear how collisions are handled here, but the basic premise + * makes sense + * - Additional structure is added before the start of each pool to define the + * pool size and the next pool in the list as this information is not + * accessible in O(good) time + */ diff --git a/src/Misc/Allocator.h b/src/Misc/Allocator.h @@ -4,8 +4,9 @@ class Allocator { public: Allocator(void); - virtual void *alloc_mem(size_t mem_size); - virtual void dealloc_mem(void *memory); + ~Allocator(void); + void *alloc_mem(size_t mem_size); + void dealloc_mem(void *memory); template <typename T, typename... Ts> T *alloc(Ts... ts) @@ -64,28 +65,34 @@ class Allocator //Return true if the current pool cannot allocate n chunks of chunk_size bool lowMemory(unsigned n, size_t chunk_size);//{(void)n;(void)chunk_size; return false;}; + bool memFree(void *pool); - void *impl; + //returns number of pools + int memPools(); + + int freePools(); + + struct AllocatorImpl *impl; }; //Memory that could either be from the heap or from the realtime allocator //This should be avoided when possible, but it's not clear if it can be avoided //in all cases -template<class T> -class HeapRtMem -{ -}; +//template<class T> +//class HeapRtMem +//{ +//}; //A helper class used to perform a series of allocations speculatively to verify //that there is enough memory for a set transation to occur. //Stuff will get weird if this ends up failing, but it will be able to at least //detect where there is an issue -class StaticAllocFreeVerifyier -{ - void *scratch_buf[4096]; - unsigned alloc_count; -}; +//class StaticAllocFreeVerifyier +//{ +// void *scratch_buf[4096]; +// unsigned alloc_count; +//}; /** diff --git a/src/Tests/AllocatorTest.h b/src/Tests/AllocatorTest.h @@ -0,0 +1,115 @@ +/* + ZynAddSubFX - a software synthesizer + + AllocatorTest.h - CxxTest for RT Memory Allocator + Copyright (C) 2014-2014 Mark McCurry + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License + as published by the Free Software Foundation. + + This program 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 General Public License (version 2 or later) for more details. + + You should have received a copy of the GNU General Public License (version 2) + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + + +#include <cxxtest/TestSuite.h> +#include <iostream> +#include <fstream> +#include <ctime> +#include <cstring> +#include <string> +#include <vector> +#include "../Misc/Allocator.h" +//using namespace std; +using std::vector; +class AllocatorTest:public CxxTest::TestSuite +{ + public: + Allocator *memory_; + vector<void*> data; + + void setUp() { + memory_ = new Allocator(); + } + + void tearDown() { + delete memory_; + } + + void testBasic() { + Allocator &memory = *memory_; + char *d = (char*)memory.alloc_mem(128); + TS_ASSERT(d); + d[0] = 0; + d[127] = 0; + memory.dealloc_mem(d); + } + + void testTooBig() { + Allocator &memory = *memory_; + //Try to allocate a gig + char *d = (char*)memory.alloc_mem(1024*1024*1024); + TS_ASSERT(d==nullptr); + } + + void testEnlarge() + { + Allocator &memory = *memory_; + //Additional Buffers + size_t N = 50*1024*1024; + char *bufA = (char*)malloc(N); + char *bufB = (char*)malloc(N); + memset(bufA,0xff, N); + memset(bufB,0xff, N); + + + //By default 25MBi is too large + //Therefore this allocation should fail + bool low = memory.lowMemory(5,5*1024*1024); + TS_ASSERT(low); + TS_ASSERT(memory.memPools() == 1); + + + //Try to add a buffer + //This provides enough for the low memory check to pass + memory.addMemory(bufA, N); + TS_ASSERT(memory.memPools() == 2); + TS_ASSERT(memory.memFree(bufA)); + bool low2 = memory.lowMemory(5,5*1024*1024); + TS_ASSERT(!low2); + TS_ASSERT(memory.memFree(bufA)); + + //We should be able to see that a chunk enters and exits the free + //state + char *mem2 = (char*)memory.alloc_mem(10*1024*1024); + TS_ASSERT(mem2); + TS_ASSERT(!memory.memFree(bufA)); + memory.dealloc_mem(mem2); + TS_ASSERT(memory.memFree(bufA)); + mem2 = (char*)memory.alloc_mem(10*1024*1024); + char *mem3 = (char*)memory.alloc_mem(10*1024*1024); + TS_ASSERT(mem3); + memory.dealloc_mem(mem2); + TS_ASSERT(!memory.memFree(bufA)); + TS_ASSERT(memory.freePools() == 0); + memory.dealloc_mem(mem3); + TS_ASSERT(memory.memFree(bufA)); + TS_ASSERT(memory.freePools() == 1); + + //Observe that adding another pool superficially works + memory.addMemory(bufB, N); + TS_ASSERT(memory.freePools() == 2); + + //delete [] bufA; + //delete [] bufB; + } + +}; diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt @@ -15,6 +15,8 @@ CXXTEST_ADD_TEST(PADnoteTest PadNoteTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PadNote CXXTEST_ADD_TEST(PluginTest PluginTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PluginTest.h) CXXTEST_ADD_TEST(UnisonTest UnisonTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/UnisonTest.h) #CXXTEST_ADD_TEST(RtAllocTest RtAllocTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RtAllocTest.h) +CXXTEST_ADD_TEST(AllocatorTest AllocatorTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/AllocatorTest.h) #Extra libraries added to make test and full compilation use the same library #links for quirky compilers @@ -34,6 +36,7 @@ target_link_libraries(PluginTest zynaddsubfx_core zynaddsubfx_nio ${GUI_LIBRARIES} ${NIO_LIBRARIES} ${AUDIO_LIBRARIES}) target_link_libraries(UnisonTest ${test_lib}) #target_link_libraries(RtAllocTest ${test_lib}) +target_link_libraries(AllocatorTest ${test_lib}) #message(STATUS "Plugin Test ${GUI_LIBRARIES} ${NIO_LIBRARIES} ${AUDIO_LIBRARIES}")