单线程环境中对象大小可变的内存池

很多系统中申请的内存大小都不是固定的,而是变化的,比如一个http服务器。http服务器获得的请求不同,不同的请求需要不同大小的内存块。那么如何构造一个可以申请不同大小内存的内存池了。其实,构造方法类似vs2008下面的debug模式中的全局new和delete的实现。思路大致是申请很多不同大小的内存块,链接起来,根据需求从内存块链的头部申请指定大小内存。
注意,本文代码中的内存池只在内存块链的头部申请内存,而且从不进行内存释放,一直到内存池使用完毕。这样的设计确实可以尽可能得提高全局的速度,只要能保证系统的使用过程中不会把内存耗光就行了。不过,我觉得如果耗光内存的可能性还是太大了。但是,你想想实现一个允许对象大小可变的内存池本来就不那么容易,而且得在充分考虑效率的前提下了。如果效率太差了,为什么不直接使用默认的new和delete表达式了。
不过,其实有一个更好的办法,我们只使用内存池一段时间,就马上删除这个内存池,再重新申请一次内存池。也就是我们不能一次使用内存池的时间太长了。困难的是我们如何确定一次使用内存池的时间,什么需要删除内存池,时间太久了也许内存会耗光的,删除频繁了点又得不到想要的效率。这些也许就需要具体情况具体对待了,也许这个内存池的实现根本不能用。

内存池代码如下,

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
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

class MemoryChunk
{
public:
MemoryChunk(MemoryChunk* nextChunk, size_t reqSize)
{
chunkSize = reqSize > DEFAULT_CHUNK_SIZE ? reqSize :
DEFAULT_CHUNK_SIZE;
next = nextChunk;
byteAlreadyAllocated = 0;
mem = new char[chunkSize];
}

~MemoryChunk()
{
delete [] mem;
}

void* alloc(size_t size)
{
void* addr = (void*)(mem + byteAlreadyAllocated);
byteAlreadyAllocated += size;
return addr;
}

//不需要释放内存块
void free(void* doomed)
{

}

MemoryChunk* nextMemChunk() { return next; }
size_t spaceAvailable()
{
return chunkSize - byteAlreadyAllocated;
}
enum {DEFAULT_CHUNK_SIZE = 4096};
private:
MemoryChunk* next;//下一个内存块
char* mem;//该内存块地址
size_t chunkSize; //该内存块的大小
size_t byteAlreadyAllocated;//该内存块被申请使用的大小
};

class ByteMemoryPool
{
public:
ByteMemoryPool(size_t initSize = MemoryChunk::DEFAULT_CHUNK_SIZE)
{
expandStorage(initSize);
}
~ByteMemoryPool();

void* alloc(size_t reqSize)
{
size_t space = listOfMemoryChunks->spaceAvailable();
if (space < reqSize)
{
expandStorage(reqSize);
}
return listOfMemoryChunks->alloc(reqSize);
}

void free(void* doomed)
{
listOfMemoryChunks->free(doomed);
}

private:
void expandStorage(size_t reqSize)
{
listOfMemoryChunks = new MemoryChunk(listOfMemoryChunks, reqSize);
}
MemoryChunk* listOfMemoryChunks;
};

ByteMemoryPool::~ByteMemoryPool()
{
MemoryChunk* memChunk = listOfMemoryChunks;
while (memChunk)
{
listOfMemoryChunks = memChunk->nextMemChunk();
delete memChunk;
memChunk = listOfMemoryChunks;
}
}

class Rational
{
public:
Rational(int a = 0, int b = 1) : n(a), d(b) {}
void* operator new(size_t size) {return memPool->alloc(size);}
void operator delete(void* doomed, size_t size) {/*memPool->free(doomed);*/}
static void newMemPool() {memPool = new ByteMemoryPool;}
static void deleteMemPool() {delete memPool;}

private:
int n;
int d;
static ByteMemoryPool* memPool;
};
ByteMemoryPool* Rational::memPool = NULL;

int main()
{
const int ARRAY_SIZE = 1000;
const int LOOP_TIMES = 5000;

Rational* array[ARRAY_SIZE];
clock_t beg = clock();
Rational::newMemPool();
for (int i = 0; i < LOOP_TIMES; ++i)
{
for (int j = 0; j < ARRAY_SIZE; ++j)
{
array[j] = new Rational(j);
}
for (int j = 0; j < ARRAY_SIZE; ++j)
{
delete array[j];
}
}
clock_t end = clock();
printf("use %f second(s).\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
system("pause");
Rational::deleteMemPool();

return 0;
}

运行效果,
与上一篇文章里面的二个内存池实现的运行时间相比,没有多大差别。

你可以好好的阅读下代码,或者自己重新敲一遍,就会发现这个内存池的实现确实简单明了,没有一点冗余的代码,但是又非常高效。如果我们采用使用一段时间就重新初始化内存池的方式就不会耗光内存,不过恰当的使用该内存池也不是件容易的事情。其实,我主要还是觉得这个版本的内存池实现主要应该用在一次的业务处理时间不会太长,或者说内存申请量不会太多的场景中

本文的思路和代码主要来自提高C++性能的编程技术一书。