From 2033d558b743ba01df3b1bbce9982b3ef2c72dc3 Mon Sep 17 00:00:00 2001 From: Artyom Beilis Date: Mon, 30 Nov 2015 15:43:23 +0000 Subject: [PATCH] replaced home written allocator with well known design --- private/buddy_allocator.h | 322 ++++++++++++++++++++++ private/hash_map.h | 1 + private/shmem_allocator.h | 668 +--------------------------------------------- tests/allocator_test.cpp | 67 ++++- 4 files changed, 393 insertions(+), 665 deletions(-) create mode 100644 private/buddy_allocator.h diff --git a/private/buddy_allocator.h b/private/buddy_allocator.h new file mode 100644 index 0000000..ba88477 --- /dev/null +++ b/private/buddy_allocator.h @@ -0,0 +1,322 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) +// +// See accompanying file COPYING.TXT file for licensing details. +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef CPPCMS_PRIVATE_BUDDY_ALLOCATOR_H +#define CPPCMS_PRIVATE_BUDDY_ALLOCATOR_H + +#include +#include +#include + +#ifdef DEBUG_ALLOCATOR +#include +#define LOG(...) do { printf("%5d:",__LINE__); printf( __VA_ARGS__ ); printf("\n"); } while(0) +#else +#define LOG(...) do {} while(0) +#endif + +#ifdef TEST_ALLOCATOR +#include +#include +#endif +namespace cppcms { +namespace impl { +class buddy_allocator { + struct page; +public: + static const int alignment_bits = (sizeof(void*) > 4 ? 4 : 3); + static const size_t alignment = (1 << alignment_bits); // 8 or 16 at 32 and 64 bit platform + + buddy_allocator(size_t memory_size) + { + assert(sizeof(*this) <= memory_size); + assert(sizeof(page) <= alignment * 2); + assert(sizeof(*this) % alignment == 0); + memory_size_ = memory_size - sizeof(*this); + max_bit_size_ = -1; + memset(free_list_,0,sizeof(free_list_)); + LOG("Usable memory %zd",memory_size_); + char *pos = memory(); + size_t reminder = memory_size_; + for(;;) { + int bits = containts_bits(reminder); + if(bits < alignment_bits + 1) + break; + size_t page_size = size_t(1) << bits; + page *p=reinterpret_cast(pos); + reminder -= page_size; + LOG("Added chunk of size %zd (%d) at pos %zx reminder %zd",page_size,bits,pos - memory(),reminder); + pos+= page_size; + p->bits = bits; + p->prev = 0; + p->next = 0; + free_list_[bits] = p; + if(max_bit_size_ == -1) + max_bit_size_ = bits; + } + } + + + void *malloc(size_t required_size) + { + size_t n = ((required_size+alignment-1)/alignment + 1)*alignment; + int bits = get_bits(n); + + + LOG("Allocating chunk of size %zd (%d) for request of %zd",n,bits,required_size); + page *p = page_alloc(bits); + LOG("Got %zx",(p?(char*)(p) - memory():0)); + if(!p) + return 0; + void *r = reinterpret_cast(p) + alignment; + return r; + } + + void free(void *ptr) + { + if(!ptr) + return; + page *p = reinterpret_cast(static_cast(ptr) - alignment); + assert(p->bits & page_in_use); + LOG("Freeing page %zx with bits %d",(char *)(p) - memory() , p->bits - page_in_use); + free_page(p); + } + + size_t total_free_memory() + { + size_t total = 0; + for(unsigned i=0;i0;bits--) { + if(free_list_[bits]) + return total_free_at(bits); + } + return 0; + } + size_t total_free_at(int bits) + { + size_t count=0; + for(page *p=free_list_[bits];p;p=p->next) + count++; + return count * ((size_t(1) << bits) - alignment); + } + +#ifdef TEST_ALLOCATOR + void test_free() { + char *p=memory(); + for(int i=max_bit_size_;i>=0;i--) { + if(free_list_[i]) { + TEST((char*)free_list_[i]==p); + TEST(free_list_[i]->bits == i); + p += 1ul << free_list_[i]->bits; + } + } + TEST(p<= memory() + memory_size_); + TEST(memory() + memory_size_ - p < int(alignment * 2)); + } + void test_and_get_free_pages(std::map *all_pages=0) + { + for(int i=0;imax_bit_size_) + TEST(free_list_[i]==0); + + for(page *p=free_list_[i];p;p=p->next) { + // check bit marks + TEST(p->bits == i); + // check linked list + if(p==free_list_[i]) + TEST(p->prev == 0); + else + TEST(p->prev->next == p); + if(p->next != 0) + TEST(p->next->prev == p); + if(all_pages) { + TEST(all_pages->find(p) == all_pages->end()); + all_pages->insert(std::make_pair(p,true)); + } + page *buddy = get_buddy(p); + if(buddy) { + TEST((buddy->bits & page_in_use) || (buddy->bits < p->bits)); + TEST((buddy->bits & 0xFF) <= p->bits); + if(all_pages && (buddy->bits & page_in_use)) + TEST(all_pages->find(buddy) == all_pages->end()); + } + } + } + } + void test_consistent(void * const *allocated,size_t allocated_size) + { + std::map all_pages; + test_and_get_free_pages(&all_pages); + for(size_t i=0;i(static_cast(allocated[i]) - alignment); + TEST(p->bits & page_in_use); + TEST(all_pages.find(p)==all_pages.end()); + all_pages[p]=false; + } + size_t pos = 0; + for(std::map::const_iterator pg=all_pages.begin();pg!=all_pages.end();++pg) { + page *p=pg->first; + bool is_free = pg->second; + TEST(is_free == !(p->bits & page_in_use)); + size_t page_pos = reinterpret_cast(pg->first) - memory(); + TEST(pos == page_pos); + size_t page_size = size_t(1) << (pg->first->bits & 0xFF); + pos+= page_size; + TEST(pos <= memory_size_); + } + TEST(memory_size_ - pos < size_t(alignment * 2)); + } + void test_consistent() + { + test_and_get_free_pages(0); + } +#endif + +private: + + static const int page_in_use = 0x100; + + struct page { + int bits; + page *next; + page *prev; + }; + + static int containts_bits(size_t n) + { + for(int i=sizeof(n)*8-2;i>0;i--) { + size_t upper = size_t(1) << (i + 1); + size_t lower = upper / 2; + if(lower <= n && n< upper) + return i; + } + return -1; + + } + static int get_bits(size_t n) + { + int i; + for(i=0;i= n) + break; + } + return i; + } + + + page *page_alloc(int bit_size) + { + LOG("Allocating page %d bits",bit_size); + if(bit_size > max_bit_size_) { + LOG("Too big size requested %d > %d",bit_size,max_bit_size_); + return 0; + } + + page *result = 0; + + if(free_list_[bit_size]==0) { + LOG("No page for bits %d, splitting",bit_size); + page *to_split = page_alloc(bit_size + 1); + if(!to_split) + return 0; + + page *unused = reinterpret_cast(reinterpret_cast(to_split) + (size_t(1)<prev = 0; + unused->next = 0; + unused->bits = bit_size; + + + free_list_[bit_size] = unused; + + result = to_split; + LOG("Got %zx; %zx is spare",(char*)to_split - memory(),(char*)unused - memory()); + } + else { + result = free_list_[bit_size]; + free_list_[bit_size]=result->next; + if(free_list_[bit_size]) + free_list_[bit_size]->prev = 0; + LOG("Using free page %zx, disconnecting",(char*)result - memory()); + } + + result->next = 0; + result->prev = 0; + result->bits = bit_size + page_in_use; + LOG("Result is %zx",(char*)result - memory()); + return result; + } + page *get_buddy(page *p) + { + size_t p_ptr = reinterpret_cast(p) - memory(); + size_t p_len = size_t(1) << p->bits; + size_t b_ptr = p_len ^ p_ptr; + if(b_ptr + p_len > memory_size_) + return 0; + return reinterpret_cast(b_ptr + memory()); + } + void free_page(page *p) + { + assert(p->bits & page_in_use); + for(;;) { + p->bits -= page_in_use; + int bits = p->bits; + LOG("Freing page with bits %d at %zx",bits,(char*)(p) - memory()); + page *buddy = get_buddy(p); + if(buddy != 0 && buddy->bits == bits) { + page *bnext = buddy->next; + page *bprev = buddy->prev; + if(bnext) + bnext->prev = bprev; + if(bprev) + bprev->next = bnext; + if(bprev==0) { + free_list_[bits]=bnext; + } + if(buddy < p) + p=buddy; + LOG("Found free buddy merging to %zx with bits %d freeing it as well",(char*)(p)-memory(),bits+1); + p->bits = (bits+1) + page_in_use; + continue; // tail recursion + } + else { + LOG("No buddy avalible - adding to free list"); + p->next = free_list_[bits]; + p->prev = 0; + if(p->next) + p->next->prev = p; + free_list_[bits] = p; + return; + } + } + } + char *memory() + { + return reinterpret_cast(this) + sizeof(*this); + } +private: + // DATA + + page *free_list_[sizeof(void*)*8]; // 16 always + size_t memory_size_; + int max_bit_size_; // at least sizeof(size_t) + size_t padding_for_alignment_[2]; +}; +} // impl +} // cppcms + +#endif diff --git a/private/hash_map.h b/private/hash_map.h index 27a83eb..45e3c23 100644 --- a/private/hash_map.h +++ b/private/hash_map.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include diff --git a/private/shmem_allocator.h b/private/shmem_allocator.h index f55b43e..0df24d3 100644 --- a/private/shmem_allocator.h +++ b/private/shmem_allocator.h @@ -16,6 +16,7 @@ #include "basic_allocator.h" #include "posix_util.h" +#include "buddy_allocator.h" #include #include @@ -24,649 +25,7 @@ namespace cppcms { namespace impl { -namespace details { -class memory_manager { -public: - - memory_manager(size_t n,int bits=12) - { - init(n,bits); - } - - void *malloc(size_t n) - { - return slab_alloc(n); - } - - void free(void *p) - { - slab_free(p); - } - - size_t get_free_memory() - { - size_t max_block = 0; - size_t last_free = 0; - for(size_t i=0;i max_block) - max_block = last_free; - } - else { - last_free=0; - } - } - return max_block * super_page_size; - } - - #ifdef TEST_ALLOCATOR - void test_free() - { - ptest_free(); - } - #endif -private: - - class segment; - - char *memory_start; - size_t map_size; - int page_bits; - size_t page_size; - size_t super_page_size; - int slab_bits; - - static const int align_bits = 4; - static const size_t align = 1 << align_bits; - static const int min_bits = align_bits + 1; - - - segment *all[sizeof(void *)*8][2]; - - unsigned long long map[1]; - - static size_t align_addr(size_t pos) - { - return ((pos + (align-1)) >> align_bits) << align_bits; - } - - static void *align_addr(void *p) - { - size_t pos = reinterpret_cast(p); - pos = align_addr(pos); - return reinterpret_cast(pos); - } - - void init(size_t n,int pb=12) - { - page_bits = pb; - page_size = 1 << page_bits; // 4096 - super_page_size = page_size * 64; - slab_bits = page_bits - min_bits; - - - map_size = (n - align_addr(sizeof(*this))) / (super_page_size + 8); - - // sizeof this contains 1 more value for map - memory_start = (char *)(this) + align_addr(sizeof(*this) + map_size * 8); - - if(n < align_addr(sizeof(*this)) || map_size == 0) { - throw cppcms::cppcms_error("The provided memory region is too small, increase cache.memory"); - } - - assert((char *)&map[map_size] <= memory_start); - assert(align_addr(memory_start) == memory_start); - assert(memory_start + super_page_size * map_size <= (char*)(this) + n); - - memset(map,0,map_size * 8); - } - - - class segment { - public: - segment(int bits,int pos) - { - memset(this,0,sizeof(*this)); - page_size_in_bits_ = bits; - segment_offset_ = pos; - } - int bits() const - { - return page_size_in_bits_; - } - int segment_size() const - { - return 1 << page_size_in_bits_; - } - int segment_count(size_t page_size) const - { - return page_size / segment_size(); - } - - int inc() - { - return ++at(0)->segment_count_; - } - int dec() - { - return --at(0)->segment_count_; - } - int count() - { - return at(0)->segment_count_; - } - void count(int n) - { - at(0)->segment_count_ = n; - } - - void push(segment *lst[2],char *memory_start) - { - prev(0,memory_start); - if(lst[0]==0) { - next(0,memory_start); - lst[0] = this; - lst[1] = this; - return; - } - next(lst[0],memory_start); - lst[0]->prev(this,memory_start); - lst[0] = this; - } - - void erase(segment *lst[2],char *memory_start) - { - if(lst[0]==this) - lst[0]=next(memory_start); - if(lst[1]==this) - lst[1]=prev(memory_start); - if(next_not_null_) { - next(memory_start)->prev(prev(memory_start),memory_start); - } - if(prev_not_null_) { - prev(memory_start)->next(next(memory_start),memory_start); - } - } - - segment *at(int pos) - { - int offset = pos - segment_offset_; - void *newp = ((char *)(this)) + offset * segment_size(); - return static_cast(newp); - } - - segment *next(char *memory_start) const - { - if(!next_not_null_) - return 0; - unsigned long long p=next_msb_; - p<<=32; - p+=next_lsb_; - return (segment *)(memory_start + p); - } - segment *prev(char *memory_start) const - { - if(!prev_not_null_) - return 0; - unsigned long long p=prev_msb_; - p<<=32; - p+=prev_lsb_; - return reinterpret_cast(memory_start + p); - } - void next(segment *p,char *memory_start) - { - if(p==0) { - next_not_null_ = 0; - return; - } - unsigned long long off = reinterpret_cast(p) - memory_start; - next_msb_ = off >> 32; - next_lsb_ = off & 0xFFFFFFFFu; - next_not_null_ = 1; - } - void prev(segment *p,char *memory_start) - { - if(p==0) { - prev_not_null_ = 0; - return; - } - unsigned long long off = reinterpret_cast(p) - memory_start; - prev_msb_ = off >> 32; - prev_lsb_ = off & 0xFFFFFFFFu; - prev_not_null_ = 1; - } - private: - uint8_t page_size_in_bits_; - uint8_t segment_count_; - uint8_t segment_offset_; - uint8_t next_not_null_ : 1; - uint8_t prev_not_null_ : 1; - uint8_t reserved_ : 6; - - uint32_t next_lsb_; // 0 - 5 - uint32_t prev_lsb_; // 0 - 5 - uint16_t next_msb_; - uint16_t prev_msb_; - }; - - struct page { - page(size_t p) - { - memset(this,0,sizeof(*this)); - pages = p; - } - uint8_t zero; // always zero - size_t pages; - }; - -#ifdef TEST_ALLOCATOR -public: - void print_stats() - { - std::cout << "Slab" << std::endl; - unsigned long long total_free = 0; - for(int i=0;inext(memory_start)) - count++; - std::cout << std::setw(8) << count << ' ' << std::setw(8) << (1ul<<(min_bits + i)) << std::endl; - total_free += (1ul<<(min_bits + i)) * count ; - } - int free_super_pages = 0; - int free_pages = 0; - int max_block = 0; - int last_free = 0; - std::cout << std::setw(16) << "Free Slab" << std::setw(16) << total_free << std::endl; - for(size_t i=0;i max_block) - max_block = last_free; - } - else { - last_free=0; - } - unsigned long long v=map[i]; - for(int i=0;i<64;i++) { - if((v & 1) == 0) { - free_pages++; - total_free += page_size; - } - v>>=1; - } - } - std::cout << std::setw(16) << "Pages" << std::setw(16) << free_pages * page_size << std::endl; - std::cout << std::setw(16) << "Super Pages" << std::setw(16) << free_super_pages * (64 * page_size) << std::endl; - std::cout << std::setw(16) << "Max Free" << std::setw(16) << (64 * page_size * max_block) << std::endl; - std::cout << std::setw(16) << "Total Free" << std::setw(16) << total_free << std::endl; - std::cout << std::setw(16) << "Inital" << std::setw(16) << super_page_size * map_size << std::endl; - } - private: -#endif - - int get_optimal_size_and_bits(size_t &n) - { - n = (((n + (align - 1)) >> (align_bits)) + 1) << align_bits; - if(n >= super_page_size) { - n=(n + super_page_size - 1) / super_page_size * super_page_size; - return -1; - } - else { - int i = get_bits(n); - n = size_t(1) << i; - if( n>= page_size) - return -1; - return i; - /* - for(i=0;i= n) - break; - } - n = 1ull << i; - if(n >= page_size) - return -1; - return i;*/ - } - } - - static void *make_offset(void *p) - { - return static_cast(p) + align; - } - - static void *remove_offset(void *p) - { - return static_cast(p) - align; - } - - void *slab_alloc(size_t n) - { - int bits = get_optimal_size_and_bits(n); - if(bits == -1) { - void *p=page_alloc(n / page_size); - if(!p) - return 0; - new(p) page(n/page_size); - return make_offset(p); - } - - segment **lst = all[bits - min_bits]; - - if(lst[0]) { - segment *p = lst[0]; - p->erase(lst,memory_start); - p->dec(); - return make_offset(p); - } - else { - void *p=page_alloc(1); - if(!p) - return 0; - segment *s = new(p) segment(bits,0); - int count = s->segment_count(page_size); - s->count(count-1); - for(int i=1;iat(i)) segment(bits,i); - s->at(i)->push(lst,memory_start); - } - return make_offset(p); - } - } - - void slab_free(void *p) - { - if(!p) - return; - p=remove_offset(p); - if(*static_cast(p)==0) { - page *pg= static_cast(p); - page_free(pg,pg->pages); - return; - } - segment *s = static_cast(p); - segment **lst = all[s->bits() - min_bits]; - s->push(lst,memory_start); - s=s->at(0); - if(s->inc() == s->segment_count(page_size)) { - int sc = s->segment_count(page_size); - for(int i=0;iat(i)->erase(lst,memory_start); - } - page_free(s,1); - } - } - - #ifdef TEST_ALLOCATOR - void ptest_free() - { - for(int i=0;i= n) - break; - } - return i; - } - */ - static int get_bits(size_t n) - { - int xh=sizeof(n)*8,xl=0,x; - while(xh>xl+1) { - x = (xh + xl)/2; - if( (1ull<= n) - xh = x; - else - xl = x; - } - if((1ull << xl)>=n) - return xl; - else - return xh; - } - - - static int find_free_bits(unsigned long long u,int n) - { - int p=0; - while(u & ((1ull << n)-1)) { - u>>=n; - p+=n; - } - return p; - } - - void *page_alloc(size_t pages) - { - int bit = get_bits(pages); - size_t i=0; - switch(bit) { - case 0: - for(i=0;i= map_size) - return 0; - int bits_needed = 1 << bit; - int pos = find_free_bits(map[i],bits_needed); - unsigned long long bmask = (1ull << bits_needed)-1; - map[i] |= (bmask << pos); - return memory_start + page_size * (64 * i + pos); - } - default: - { - size_t n = pages / 64; - bool found = false; - if(n==1) { - for(i=0;i0 && map[i+k]==0) { - k/=2; - } - i+=k+1; - continue; - } - found = true; - for(size_t j=1;j(p) - memory_start) / super_page_size; - if(bit<=5) { - { - int pos = (static_cast(p) - memory_start) % (super_page_size) / page_size; - int bits_needed = 1 << bit; - unsigned long long bmask = (1ull << bits_needed)-1; - map[i] &= ~(bmask << pos); - } - } - else { - n /= 64; - while(n>0) { - map[i]=0; - n--; - i++; - } - } - } - - - -}; // memory_manager - -} // details class shmem_control : public booster::noncopyable{ public: shmem_control(size_t size) : @@ -674,10 +33,13 @@ public: region_(mmap_anonymous(size)), memory_(0) { - memory_ = new (region_) details::memory_manager(size_); + if(size < sizeof(*memory_)) + throw cppcms_error("shared memory size is too small"); + memory_ = new (region_) cppcms::impl::buddy_allocator(size_); } ~shmem_control() { + memory_->~buddy_allocator(); ::munmap(region_,size_); } inline size_t size() @@ -687,9 +49,14 @@ public: inline size_t available() { mutex::guard g(lock_); - return memory_->get_free_memory(); + return memory_->total_free_memory(); } + inline size_t max_available() + { + mutex::guard g(lock_); + return memory_->max_free_chunk(); + } inline void *malloc(size_t s) { mutex::guard g(lock_); @@ -701,21 +68,11 @@ public: return memory_->free(p); } - #ifdef TEST_ALLOCATOR - void test_free() - { - memory_->test_free(); - } - void print_stats() - { - memory_->print_stats(); - } - #endif private: mutex lock_; size_t size_; void *region_; - details::memory_manager *memory_; + cppcms::impl::buddy_allocator *memory_; }; template @@ -754,7 +111,6 @@ public : }; - } // } // diff --git a/tests/allocator_test.cpp b/tests/allocator_test.cpp index 6661307..3bd5937 100644 --- a/tests/allocator_test.cpp +++ b/tests/allocator_test.cpp @@ -6,12 +6,16 @@ // /////////////////////////////////////////////////////////////////////////////// #define TEST_ALLOCATOR -#include "shmem_allocator.h" +//#define DEBUG_ALLOCATOR #include "test.h" +#include "buddy_allocator.h" +#include #include #include +#include #include +#include static const size_t objects = 100; @@ -35,20 +39,27 @@ void check_all() int main() { + size_t memory_size = 1024*1024*10ULL; + void *ptr = malloc(memory_size); + cppcms::impl::buddy_allocator *buddy=new(ptr) cppcms::impl::buddy_allocator(memory_size); try{ int fail = 0; - cppcms::impl::shmem_control ct(1024*1024*10ULL); + int limit = 1000; + buddy->test_free(); for(int i=0;ifree(memory[pos]); memory[pos] = 0; check_all(); } - if((memory[pos]=ct.malloc(size))==0) { + if((memory[pos]=buddy->malloc(size))==0) { check_all(); fail++; } @@ -61,22 +72,60 @@ int main() s[j] = c; check_all(); } + buddy->test_consistent(memory,objects); } for(size_t i=0;ifree(memory[i]); memory[i]=0; check_all(); } + buddy->test_consistent(memory,objects); + } + + buddy->test_free(); + std::cout << "\n malloc fail to all " << double(fail) / limit * 100 << std::endl; + for(int dir=0;dir<=1;dir++) { + for(size_t i=1;i ptrs; + void *tmp=0; + size_t exp = memory_size / i; + while((tmp=buddy->malloc(i))!=0) { + ptrs.push_back(tmp); + if(ptrs.size() * 100 % exp==0) + buddy->test_consistent(&ptrs[0],ptrs.size()); + } + if(ptrs.empty()) + break; + buddy->test_consistent(&ptrs[0],ptrs.size()); + if(dir == 0) { + for(size_t i=0;ifree(ptrs[i]); + ptrs[i]=0; + if(i*100 % ptrs.size() == 0) + buddy->test_consistent(&ptrs[0]+i,ptrs.size()-i); + } + } + else { + for(int i=ptrs.size()-1;i>=0;i--) { + buddy->free(ptrs[i]); + ptrs[i]=0; + if(i*100 % ptrs.size() == 0) + buddy->test_consistent(&ptrs[0],i); + } + } + buddy->test_free(); + } } - ct.test_free(); - std::cout << double(fail) / limit * 100 << std::endl; } catch(std::exception const &e) { - std::cerr << "Fail " << e.what() << booster::trace(e) << std::endl; + std::cerr << "Fail " << e.what() << std::endl; return 1; } + buddy->~buddy_allocator(); + free(ptr); std::cout << "Ok\n"; }