少女祈祷中...

作为程序员(C/C++)我们知道申请内存使用的是malloc,malloc其实就是一个通用的大众货,什么场景 下都可以用,但是什么场景下都可以用就意味着什么场景下都不会有很高的性能

解决固定大小的内存申请释放需求, 性能达到极致,不考虑内存碎片等问题

设计思想:先分配一块大内存(定长内存), 还需要一个自由链表来管理还回来的内存 freelist,再记录一下内存块剩余字节数

1
2
3
char* _memory = nullptr;  //指向大块内存的指针, char* 有利于拓展
size_t _remainSizeBytes = 0;//剩余
void* _freelist = nullptr; //自由链表管理还回来的内存

申请内存时

1
2
3
4
5
if(_memory == nullptr) {
//malloc 分配定长内存
}
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; //申请128字节内存块
_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版本有更明显的效果