c++ 的高性能内存池 一般,使用malloc free
和 new delete
来动态管理内存,因为牵扯到系统调用,因此效率很低。内存池的思想就是使用很少的系统调用,申请一大块内存自己管理。内存池向系统申请大块内存,然后再分为小片给程序使用,每次请求内存,起始都是在内存池里拿内存而非通过系统调用。(线程池和对象池有着类似的思想)。内存池的优点:
原理上比malloc和new快
分配内存时不存在开销(overhead)
几乎不存在内存碎片(为什么???)
无需一个个释放对象,只需要释放内存池即可
缺点:
1 2 3 4 5 6 MemoryPool& operator =(const MemoryPool& mp) = delete ; MemoryPool& operator =(const MemoryPool&& mp) noexcept ;
1 2 static_assert (BlockSize >= 2 * sizeof (Slot_), "BlockSize too small." )
每个slot作为一个块
1 2 3 4 union Slot_ { value_type element; Slot_* next; };
一个奇怪的构造函数
1 2 3 4 5 6 7 8 9 MemoryPool (const MemoryPool& mp) noexcept ; template <typename T, size_t BlockSize>MemoryPool<T, BlockSize>::MemoryPool (const MemoryPool& mp) noexcept : MemoryPool () {}
析构函数中的强制类型转换
1 2 3 4 5 6 7 8 9 10 11 template <typename T, size_t BlockSize>MemoryPool<T, BlockSize>::~MemoryPool () noexcept { slot_pointer_ curr = currentBlock_; while (curr != nullptr ) { slot_pointer_ prev = curr->next; operator delete (reinterpret_cast <void *>(curr)) ; curr = prev; } }
1 2 3 4 Slot_ *currentBlock_; Slot_ *currentSlot_; Slot_ *lastSlot_; Slot_ *freeSlots_;
内存池拥有四个指针,freeSlots应该就是空闲slot
一个内存池MemoryAlloc包含若干个block块,空闲内存块通过链表的形式记录,block内以slot呈现。
首先我们使用 typedef
定义了某个东西成为了 allocator
,这是毫无疑问的,而这个东西却是:
1 typename Alloc::template rebind<Node>::other
它长得很奇怪,这其实是为了解决让编译器不认识代码的问题而出现的写法(语法)。
首先我们定义了 Alloc = std::allocator<T>
,而 rebin
d 其实是 std::allocator
的一个成员。巧就巧在,rebind
本身又是另一个模板, C++ 称其为 dependent name
。完整的形式本来应该是:
1 std::allocator<T>::rebind<Node>::other
但是模板的相关解析已经在 <T>
出现过了,后面的 <Node>
中的 <
只能被解释为小于符号,这会导致编译出错。为了表示 dependent name
是一个模板,就必须使用 template
前缀。如果没有 template
前缀,<
会被编译器解释为小于符号。所以,我们必须写成下面的形式:
1 std::allocator<T>::template rebind<Node>::other
最后,编译器在其实根本没有任何办法来区分 other
究竟是一个类型,还是一个成员。但我们其实知道 other
是一个类型,所以使用 typename 来明确指出这是一个类型,最终才有了:
1 typename std::allocator<T>::template rebind<Node>::other
四个文件
memory_pool
|– MemoryPool.h
|– MemoryPool.cpp
|– StackAlloc.h
|– Test.cpp
MemoryPool.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 #ifndef TINY_RECORD_MEMORYPOOL_H #define TINY_RECORD_MEMORYPOOL_H #pragma once #include <cstddef> #include <cstdint> #include <utility> template <typename T, size_t BlockSize = 4096 > class MemoryPool {public : template <typename U> struct rebind{ typedef MemoryPool<U> other; }; MemoryPool () noexcept ; MemoryPool (const MemoryPool &mem) noexcept ; MemoryPool (MemoryPool &&mem) noexcept ; template <typename U> MemoryPool (const MemoryPool<U> &mem) noexcept ; ~MemoryPool () noexcept ; MemoryPool &operator = (const MemoryPool &mem) = delete ; MemoryPool &operator = (MemoryPool &&mem) noexcept ; inline T *address (T &element) const noexcept {return &element;} inline const T *address (const T &element) const noexcept {return &element;} inline T *allocate (size_t n = 1 , const T *hint = nullptr ) { if (freeSlot_ !=nullptr ){ T *result = reinterpret_cast <T *>(freeSlot_); freeSlot_ = freeSlot_->next; return result; }else { if (currentSlot_ >= lastSlot_){ allocateBlock (); } return reinterpret_cast <T *>(currentSlot_++); } } inline void deallocate (T *p, size_t n = 1 ) { if (p != nullptr ){ reinterpret_cast <Slot_ *>(p)->next = freeSlot_; freeSlot_ = reinterpret_cast <Slot_ *>(p); } } template <typename U, typename ... Args> inline void construct (U* p, Args &&... args) { new (p) U (std::forward<Args>(args)...); } template <typename U> inline void destroy (U *p) { p->~U (); } template <typename ... Args> inline T *newElement (Args &&... args) { T *result = allocate (); construct (result, std::forward<Args>(args)...); return result; } inline void deleteElement (T *p) { if (p != nullptr ){ p->~T (); deallocate (p); } } inline size_t max_size () const noexcept { size_t max_Block = -1 /BlockSize; size_t max_T = (BlockSize - sizeof (char *))/sizeof (Slot_); return max_Block*max_T; } private : union Slot_ { T element; Slot_ *next; }; Slot_ *currentBlock_; Slot_ *currentSlot_; Slot_ *lastSlot_; Slot_ *freeSlot_; void allocateBlock () ; inline size_t padPointer (char * p, size_t align) const noexcept { uintptr_t addr = reinterpret_cast <uintptr_t >(p); size_t padding = (align - addr) % align; return padding; } static_assert (BlockSize >= 2 *sizeof (Slot_),"static_assert: Block is too small!" ); }; #endif
MemoryPool.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include "MemoryPool.h" using namespace std;template <typename T, size_t BlockSize>MemoryPool<T, BlockSize>::MemoryPool () noexcept { currentBlock_ = nullptr ; currentSlot_ = nullptr ; lastSlot_ = nullptr ; freeSlot_ = nullptr ; } template <typename T, size_t BlockSize>MemoryPool< T, BlockSize>::MemoryPool (const MemoryPool &mem) noexcept :MemoryPool () {} template <typename T, size_t BlockSize>MemoryPool<T, BlockSize>::MemoryPool (MemoryPool &&mem) noexcept { currentBlock_ = mem.currentBlock_; mem.currentBlock_ = nullptr ; currentSlot_ = mem.currentSlot_; lastSlot_ = mem.lastSlot_; freeSlot_ = mem.freeSlot_; } template <typename T, size_t BlockSize>template <typename U>MemoryPool< T, BlockSize>::MemoryPool (const MemoryPool<U> &mem) noexcept :MemoryPool () {} template <typename T, size_t BlockSize>MemoryPool<T, BlockSize>::~MemoryPool () noexcept { Slot_ * cur = currentBlock_; while (cur !=nullptr ){ Slot_ *nxt = cur->next; ::operator delete (reinterpret_cast <void *>(cur)) ; cur = nxt; } } template <typename T, size_t BlockSize>MemoryPool<T, BlockSize> &MemoryPool<T, BlockSize>:: operator =(MemoryPool &&mem) noexcept { if (this != &mem){ std::swap (currentBlock_, mem.currentBlock_); currentSlot_ = mem.currentSlot_; lastSlot_ = mem.lastSlot_; freeSlot_ = mem.freeSlot_; } return *this ; } template <typename T, size_t BlockSize>void MemoryPool<T, BlockSize>::allocateBlock () { char *newBlock = reinterpret_cast <char *>(::operator new (BlockSize)); reinterpret_cast <Slot_ *>(newBlock)->next = currentBlock_; currentBlock_ = reinterpret_cast <Slot_ *>(newBlock); char *curPoint = newBlock + sizeof (Slot_ *); size_t padding = padPointer (curPoint, alignof (Slot_)); currentSlot_ = reinterpret_cast <Slot_ *>(curPoint + padding); lastSlot_ = reinterpret_cast <Slot_ *>(newBlock + BlockSize - sizeof (Slot_) + 1 ); }
StackAlloc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #ifndef TINY_RECORD_STACKALLOC_H #define TINY_RECORD_STACKALLOC_H #include <memory> template <typename T>struct StackNode_ { T data; StackNode_* prev; }; template <typename T, typename alloc = std::allocator<T>>class StackAlloc { public : typedef StackNode_<T> node; typedef typename alloc::template rebind<node>::other allocator; StackAlloc (){head_ = 0 ;} ~StackAlloc (){clear ();} bool isEmpty () { return (head_ == 0 ); } void clear () { node* cur = head_; while (cur!=0 ){ node* temp = cur->prev; allocator_.destroy (cur); allocator_.deallocate (cur,1 ); cur = temp; } head_ = 0 ; } void push (T element) { node* newEle = allocator_.allocate (1 ); allocator_.construct (newEle, node ()); newEle->data = element; newEle->prev = head_; head_ = newEle; } T top () { return head_->data; } T pop () { node* cur = head_->prev; T cur_ele = head_->data; allocator_.destroy (head_); allocator_.deallocate (head_,1 ); head_ = cur; return cur_ele; } private : allocator allocator_; node* head_; }; #endif
Test.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 #include <iostream> #include <cassert> #include <time.h> #include <vector> #include "MemoryPool.cpp" #include "StackAlloc.h" #define MAXELES 100000 #define REPS 50 using namespace std;int main () { cout<< "======Use default allocator======" <<endl; clock_t start; StackAlloc<int , std::allocator<int >> stackDefault; start = clock (); for (int i = 0 ;i<REPS;++i){ assert (stackDefault.isEmpty ()); for (int j = 0 ;j<MAXELES/4 ;++j){ stackDefault.push (j); stackDefault.push (j); stackDefault.push (j); stackDefault.push (j); } for (int j = 0 ;j<MAXELES/4 ;++j){ stackDefault.pop (); stackDefault.pop (); stackDefault.pop (); stackDefault.pop (); } } cout<<"default allocator time: " ; cout<< ((double )(clock () - start)/CLOCKS_PER_SEC) <<endl; cout<< "======Use MemoryPool======" <<endl; StackAlloc<int , MemoryPool<int >> stackWithMem; start = clock (); for (int i = 0 ;i<REPS;++i){ assert (stackWithMem.isEmpty ()); for (int j = 0 ;j<MAXELES/4 ;++j){ stackWithMem.push (j); stackWithMem.push (j); stackWithMem.push (j); stackWithMem.push (j); } for (int j = 0 ;j<MAXELES/4 ;++j){ stackWithMem.pop (); stackWithMem.pop (); stackWithMem.pop (); stackWithMem.pop (); } } cout<<"default allocator time: " ; cout<< ((double )(clock () - start)/CLOCKS_PER_SEC) <<endl; cout<< "======Use vector======" <<endl; std::vector<int > vec; start = clock (); for (int i = 0 ;i<REPS;++i){ assert (vec.empty ()); for (int j = 0 ;j<MAXELES/4 ;++j){ vec.push_back (j); vec.push_back (j); vec.push_back (j); vec.push_back (j); } for (int j = 0 ;j<MAXELES/4 ;++j){ vec.pop_back (); vec.pop_back (); vec.pop_back (); vec.pop_back (); } } cout<<"vector time: " ; cout<< ((double )(clock () - start)/CLOCKS_PER_SEC) <<endl; return 0 ; }