什么是内存池?什么是 C++ 的 allocator?
内存池简单说,是为了减少频繁使用 malloc/free new/delete 等系统调用而造成的性能损耗而设计的。当我们的程序需要频繁地申请和释放内存时,频繁地使用内存管理的系统调用可能会造成性能的瓶颈,嗯,是可能,毕竟操作系统的设计也不是盖的(麻麻说把话说太满会被打脸的(⊙v⊙))。内存池的思想是申请较大的一块内存(不够时继续申请),之后把内存管理放在应用层执行,减少系统调用的开销。
那么,allocator 呢?它默默的工作在 C++ 所有容器的内存分配上。默默贴几个链接吧:
http://www.cnblogs.com/wpcockroach/archive/2012/05/10/2493564.html
http://blog.csdn.net/justaipanda/article/details/7790355
http://www.cplusplus.com/reference/memory/allocator/
http://www.cplusplus.com/reference/memory/allocator_traits/
当你对 allocator 有基本的了解之后,再看这个项目应该会有恍然大悟的感觉,因为这个内存池是以一个 allocator 的标准来实现的。一开始不明白项目里很多函数的定义是为了什么,结果初步了解了 allocator 后才知道大部分是标准接口。这样一个 memory pool allocator 可以与大多数 STL 容器兼容,也可以应用于你自定义的类。像作者给出的例子 —— test.cpp, 是用一个基于自己写的 stack 来做 memory pool allocator 和 std::allocator 性能的对比 —— 最后当然是 memory pool allocator 更优。
项目:
Github:MemoryPool
基本使用:
因为这是一个 allocator 类,所以所有使用 std::allocator 的地方都可以使用这个 MemoryPool。在项目的 test.cpp 中,MemoryPool 作为 allocator 用于 StackAlloc(作者实现的 demo 类) 的内存管理类。定义如下:
- StackAlloc<int, MemoryPool<int> > stackPool;
其次,你也可以将其直接作为任一类型的内存池,用 newElement 创建新元素,deleteElement 释放元素,就像 new/delete 一样。用下面的例子和 new/delete 做对比:
- #include <iostream>
- #include <cassert>
- #include <time.h>
- #include <vector>
- #include <stack>
-
- #include "MemoryPool.h"
-
- using namespace std;
-
-
- #define ELEMS 1000000
- #define REPS 50
-
- int main()
- {
-
- clock_t start;
-
- MemoryPool<size_t> pool;
- start = clock();
- for(int i = 0;i < REPS;++i)
- {
- for(int j = 0;j< ELEMS;++j)
- {
-
- size_t* x = pool.newElement();
-
-
- pool.deleteElement(x);
- }
- }
- std::cout << "MemoryPool Time: ";
- std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
-
-
- start = clock();
- for(int i = 0;i < REPS;++i)
- {
- for(int j = 0;j< ELEMS;++j)
- {
- size_t* x = new size_t;
-
- delete x;
- }
- }
- std::cout << "new/delete Time: ";
- std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
-
- return 0;
-
- }
运行的结果是:
MemoryPool Time: 1.93389
new/delete Time: 4.64903
嗯,内存池快了一倍多。如果是自定义的类的话这个差距应该还会更大一点。
代码分析:
项目的实现有 C++11 和 C++98 两个版本,C++11 版本似乎更加高效,不过个人 C++11 了解不多,就以 C++98 版本来分析吧。
主要函数:
allocate 分配一个对象所需的内存空间
deallocate 释放一个对象的内存(归还给内存池,不是给操作系统)
construct 在已申请的内存空间上构造对象
destroy 析构对象
newElement 从内存池申请一个对象所需空间,并调用对象的构造函数
deleteElement 析构对象,将内存空间归还给内存池
allocateBlock 从操作系统申请一整块内存放入内存池
关键知识点:
理解项目的关键在于理解 placement new 和 union 的用法.
placement new: http://blog.csdn.net/zhangxinrun/article/details/5940019
union:http://www.cnblogs.com/BeyondTechnology/archive/2010/09/19/1831293.html
关于 union 的使用觉得好巧妙,这是相应的定义:
- union Slot_ {
- value_type element;
- Slot_* next;
- };
Slot_ 在创建对象的时候存放对象的值,当这个对象被释放时这块内存作为一个 Slot_* 指针放入 free 的链表。所以 Slot_ 既可以用来存放对象,又可以用来构造链表。
工作原理:
内存池是一个一个的 block 以链表的形式连接起来,每一个 block 是一块大的内存,当内存池的内存不足的时候,就会向操作系统申请新的 block 加入链表。还有一个 freeSlots_ 的链表,链表里面的每一项都是对象被释放后归还给内存池的空间,内存池刚创建时 freeSlots_ 是空的,之后随着用户创建对象,再将对象释放掉,这时候要把内存归还给内存池,怎么归还呢?就是把指向这个对象的内存的指针加到 freeSlots_ 链表的前面(前插)。
用户在创建对象的时候,先检查 freeSlots_ 是否为空,不为空的时候直接取出一项作为分配出的空间。否则就在当前 block 内取出一个 Slot_ 大小的内存分配出去,如果 block 里面的内存已经使用完了呢?就向操作系统申请一个新的 block。
内存池工作期间的内存只会增长,不释放给操作系统。直到内存池销毁的时候,才把所有的 block delete 掉。
注释源码:
点我到 Github
头文件:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #ifndef MEMORY_POOL_H
- #define MEMORY_POOL_H
-
- #include <limits.h>
- #include <stddef.h>
-
- template <typename T, size_t BlockSize = 4096>
- class MemoryPool
- {
- public:
-
- typedef T value_type;
- typedef T* pointer;
- typedef T& reference;
- typedef const T* const_pointer;
- typedef const T& const_reference;
- typedef size_t size_type;
- typedef ptrdiff_t difference_type;
-
- template <typename U> struct rebind {
- typedef MemoryPool<U> other;
- };
-
-
-
- MemoryPool() throw();
- MemoryPool(const MemoryPool& memoryPool) throw();
- template <class U> MemoryPool(const MemoryPool<U>& memoryPool) throw();
-
-
- ~MemoryPool() throw();
-
-
- pointer address(reference x) const throw();
- const_pointer address(const_reference x) const throw();
-
-
-
- pointer allocate(size_type n = 1, const_pointer hint = 0);
- void deallocate(pointer p, size_type n = 1);
-
-
- size_type max_size() const throw();
-
-
- void construct(pointer p, const_reference val);
- void destroy(pointer p);
-
-
- pointer newElement(const_reference val);
- void deleteElement(pointer p);
-
- private:
-
- union Slot_ {
- value_type element;
- Slot_* next;
- };
-
- typedef char* data_pointer_;
- typedef Slot_ slot_type_;
- typedef Slot_* slot_pointer_;
-
- slot_pointer_ currentBlock_;
- slot_pointer_ currentSlot_;
- slot_pointer_ lastSlot_;
- slot_pointer_ freeSlots_;
-
- size_type padPointer(data_pointer_ p, size_type align) const throw();
- void allocateBlock();
-
-
-
- };
-
- #include "MemoryPool.tcc"
-
- #endif // MEMORY_POOL_H
实现文件:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #ifndef MEMORY_BLOCK_TCC
- #define MEMORY_BLOCK_TCC
-
-
- template <typename T, size_t BlockSize>
- inline typename MemoryPool<T, BlockSize>::size_type
- MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
- const throw()
- {
- size_t result = reinterpret_cast<size_t>(p);
- return ((align - result) % align);
- }
-
-
- template <typename T, size_t BlockSize>
- MemoryPool<T, BlockSize>::MemoryPool()
- throw()
- {
- currentBlock_ = 0;
- currentSlot_ = 0;
- lastSlot_ = 0;
- freeSlots_ = 0;
- }
-
-
- template <typename T, size_t BlockSize>
- MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool& memoryPool)
- throw()
- {
- MemoryPool();
- }
-
-
- template <typename T, size_t BlockSize>
- template<class U>
- MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool<U>& memoryPool)
- throw()
- {
- MemoryPool();
- }
-
-
- template <typename T, size_t BlockSize>
- MemoryPool<T, BlockSize>::~MemoryPool()
- throw()
- {
- slot_pointer_ curr = currentBlock_;
- while (curr != 0) {
- slot_pointer_ prev = curr->next;
-
- operator delete(reinterpret_cast<void*>(curr));
- curr = prev;
- }
- }
-
-
- template <typename T, size_t BlockSize>
- inline typename MemoryPool<T, BlockSize>::pointer
- MemoryPool<T, BlockSize>::address(reference x)
- const throw()
- {
- return &x;
- }
-
-
- template <typename T, size_t BlockSize>
- inline typename MemoryPool<T, BlockSize>::const_pointer
- MemoryPool<T, BlockSize>::address(const_reference x)
- const throw()
- {
- return &x;
- }
-
-
- template <typename T, size_t BlockSize>
- void
- MemoryPool<T, BlockSize>::allocateBlock()
- {
-
-
- data_pointer_ newBlock = reinterpret_cast<data_pointer_>
- (operator new(BlockSize));
-
- reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;
-
- currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);
-
- data_pointer_ body = newBlock + sizeof(slot_pointer_);
-
- size_type bodyPadding = padPointer(body, sizeof(slot_type_));
-
- currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
-
- lastSlot_ = reinterpret_cast<slot_pointer_>
- (newBlock + BlockSize - sizeof(slot_type_) + 1);
- }
-
-
- template <typename T, size_t BlockSize>
- inline typename MemoryPool<T, BlockSize>::pointer
- MemoryPool<T, BlockSize>::allocate(size_type, const_pointer)
- {
-
- if (freeSlots_ != 0) {
- pointer result = reinterpret_cast<pointer>(freeSlots_);
-
- freeSlots_ = freeSlots_->next;
- return result;
- }
- else {
- if (currentSlot_ >= lastSlot_)
-
- allocateBlock();
-
- return reinterpret_cast<pointer>(currentSlot_++);
- }
- }
-
-
- template <typename T, size_t BlockSize>
- inline void
- MemoryPool<T, BlockSize>::deallocate(pointer p, size_type)
- {
- if (p != 0) {
-
- reinterpret_cast<slot_pointer_>(p)->next = freeSlots_;
-
- freeSlots_ = reinterpret_cast<slot_pointer_>(p);
- }
- }
-
-
- template <typename T, size_t BlockSize>
- inline typename MemoryPool<T, BlockSize>::size_type
- MemoryPool<T, BlockSize>::max_size()
- const throw()
- {
- size_type maxBlocks = -1 / BlockSize;
- return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
- }
-
-
- template <typename T, size_t BlockSize>
- inline void
- MemoryPool<T, BlockSize>::construct(pointer p, const_reference val)
- {
-
- new (p) value_type (val);
- }
-
-
- template <typename T, size_t BlockSize>
- inline void
- MemoryPool<T, BlockSize>::destroy(pointer p)
- {
-
- p->~value_type();
- }
-
-
- template <typename T, size_t BlockSize>
- inline typename MemoryPool<T, BlockSize>::pointer
- MemoryPool<T, BlockSize>::newElement(const_reference val)
- {
-
- pointer result = allocate();
-
- construct(result, val);
- return result;
- }
-
-
- template <typename T, size_t BlockSize>
- inline void
- MemoryPool<T, BlockSize>::deleteElement(pointer p)
- {
- if (p != 0) {
-
- p->~value_type();
-
- deallocate(p);
- }
- }
-
- #endif // MEMORY_BLOCK_TCC