作为程序员(C/C++)我们知道申请内存使用的是malloc,malloc其实就是一个通用的大众货,什么场景 下都可以用,但是什么场景下都可以用就意味着什么场景下都不会有很高的性能
解决固定大小的内存申请释放需求, 性能达到极致,不考虑内存碎片等问题
设计思想:先分配一块大内存(定长内存), 还需要一个自由链表来管理还回来的内存 freelist,再记录一下内存块剩余字节数
1 2 3
| char* _memory = nullptr; size_t _remainSizeBytes = 0; void* _freelist = nullptr;
|
申请内存时
1 2 3 4 5
| if(_memory == nullptr) { } T*obj = (T*)_memory; _memory += sizeof(T);
|
存在问题, 如果最后一块内存 < sizeof(T), 内存溢出了。所以需要记录剩余字节数 _remainSizeBytes;
优化一下申请内存的代码
1 2 3 4 5 6 7 8 9 10 11
| if (_remainSizeBytes < sizeof(T)) { _remainSizeBytes = 128 * 1024; _memory = (char*)malloc(128 * 1024); if (_memory == nullptr) { throw std::bad_alloc(); } } obj = (T*)_memory; _memory += objsize; _remainSizeBytes -= objsize;
|
还回来时(Delete)怎么处理呢
1 2
| *(void**)obj = _freelist; _freelist = obj;
|
先把obj强转为(void**), 这个操作比较巧妙也危险, 对于不同的机器也适用(32位机器 和 64位机器)
这里将 obj 指针强制转换为 void** 类型,也就是指向指针的指针类型,然后对其解引用,实际上就是把 obj 所指向的内存位置当作一个指针变量来看待,并将当前自由链表的头指针 _freelist 的值赋给它。本质上是让当前要回收的对象(通过其内存空间的特定位置)记录下原来自由链表头的位置,相当于为后续插入操作做准备
然后申请内存时也要优先使用自由链表中的内存
1 2 3 4 5 6
| if (_freelist) { void* next = *((void**)_freelist); obj = _freelist; _freelist = next; }
|
但是分配内存时还存在着问题,
1
| size_t objsize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
|
分配内存大小要满足上面代码, 方便还回来的时候连接到一起
定长内存池的设计
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
| #pragma once
#include <iostream> #include <new> using std::cout; using std::endl; template<class T> class ObjectPool { public: T* New() { T* obj = nullptr; if (_freelist) { void* next = *((void**)_freelist); obj = (T*)_freelist; _freelist = next; } else { if (_remainSizeBytes < sizeof(T)) { _remainSizeBytes = 128 * 1024; _memory = (char*)malloc(128 * 1024); if (_memory == nullptr) { throw std::bad_alloc(); } } obj = (T*)_memory; size_t objsize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T); _memory += objsize; _remainSizeBytes -= objsize; } new(obj)T; return obj; }
void Delete(T* obj) { obj->~T();
*(void**)obj = _freelist; _freelist = obj; } private: char* _memory = nullptr; size_t _remainSizeBytes = 0; void* _freelist = nullptr; };
|
测试代码
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
| #include "ObjectPool.h" #include <vector>
struct TreeNode { int _val; TreeNode* _left; TreeNode* _right; TreeNode() :_val(0) , _left(nullptr) , _right(nullptr) {} }; void TestObjectPool() { const size_t Rounds = 10; const size_t N = 1000000; size_t begin1 = clock(); std::vector<TreeNode*> v1; v1.reserve(N); for (size_t j = 0; j < Rounds; ++j) { for (int i = 0; i < N; ++i) { v1.push_back(new TreeNode); } for (int i = 0; i < N; ++i) { delete v1[i]; } v1.clear(); } size_t end1 = clock(); ObjectPool<TreeNode> TNPool; size_t begin2 = clock(); std::vector<TreeNode*> v2; v2.reserve(N); for (size_t j = 0; j < Rounds; ++j) { for (int i = 0; i < N; ++i) { v2.push_back(TNPool.New()); } for (int i = 0; i < 100000; ++i) { TNPool.Delete(v2[i]); } v2.clear(); } size_t end2 = clock(); cout << "new cost time:" << end1 - begin1 << endl; cout << "object pool cost time:" << end2 - begin2 << endl; }
int main() { TestObjectPool(); return 0; }
|
release版本有更明显的效果