频繁的分配和回收内存会严重降低程序的性能。原因在于默认的内存管理器是通用的、应用程序可能会以某种特定的方式使用内存,并且为不需要的功能付出性能上的代价。通过开发专用的内存管理器可以解决这个问题。
两个方面考虑内存管理器的设计:大小和并发
固定大小、可变大小
单线程:内存管理器局限在一个线程内,内存被一个线程使用
多线程:内存管理器被多个线程并发的使用,需包含互斥执行的代码段。无论什么时候,只有一个线程在执行一个代码段
- 全局内存管理器(由new()和delete()执行)是通用的。因此代价较高
- 专用内存管理器,如单线程内存管理器,其可以省去new和delete的并发问题
(指的是对于一个类重载实现他的new和delete方面。分配的大小根据sizeof,然后用new char[]来分配,delete[] 来删除)
示例:
- 全局的new和delete
class Rational{
public:
Rational (int a = 0, int b = 1) : n(a), d(b) {}
private:
int n; //分子
int d; //分母
};
Rational *array = new Rational(i);
delete array; // 循环执行100万次,1500ms
版本1:专用的Rational内存管理器
避免频繁使用默认内存管理器,Rational需要维护一个预先分配的Rational对象的静态链接链表,列出空闲的可用对象,当需要Rational对象时,从空闲列表取出一个即可,使用后再把它放回空闲列表以便今后分配。
class NextOnFreeList{
public:
NextOnFreeList *next;
};
空闲列表被声明为一个有NextOnFreeList元素组成的列表,这样空闲列表的每个元素都声明为NextOnFreeList结构,但他们也是Rational对象。
为了遍历对象,每个Rational对象的前几个字节用于指向空闲列表的下一个对象(next)。可以通过将Rational对象转换成指向NextOnFreeList类型的指针来实现。这样空闲列表双重身份:Rational对象的序列andNextOnFreeList元素的序列
class Rational{
...
static NextOnFreeList *freelist;
};
空闲列表声明为Rational静态成员,Rational的New和delete操作符可以管理该静态列表,重载全局的new和delete。
class Rational{
public:
inline void *operator new (size_t size);
inline void operator delete (void *doomed, size_t size);
static void newMemPool() { expandTheFreeList(); }
static void deleteMemPool();
private:
static NextOnFreeList *freelist;
static void expandTheFreeList();
enum { EXPANSION_SIZE = 32};
int n;
int d;
};
new()是在空闲列表中分配一个Rational对象,列表为空就扩展列表。先去掉空闲列表的头部,调整完指针后再返回head。
inline void* Rational::operator new(szie_t size) {
if(0 == freelist) {
expandTheFreeList();
}
NextOnFreeList *head = freelist;
freelist = head->next;
return head;
}
delete()把Rational对象直接添加到空闲列表的头部,以返回一个Rational对象。这样每次delete都会使得freelist变为delete调的空间,而其next则指向之前的freelist,因此可以继续形成一条链
inline void Rational::operator delete(void *doomed, size_t size) {
NextOnFreeList *head = static_cast<NextOnFreeList *> doomed;
head->next = freelist;
freelist = head;
}
Rational和NextOnFreeList之间的类型转换有危险。必须确保空闲列表的元素足够大以支持任意一种类型。因此,当我们用Rational对象填充空闲列表时,需要比较两个类型之间的大小,从而分配较大的一个。
void Rational::expandTheFreeList() {
size_t size = (sizeof(Rational) > sizeof(NextOnFreeList *)) ?
sizeof(Rational) : sizeof(NextOnFreeList *);
NextOnFreeList *runner = static_cast<NextOnFreeList *> new char[size];
freelist = runner;
for(int i = 0; i < EXPANSION_SIZE; i++) {
runner->next = static_cast<NextOnFreeList *> new char[size];
runner = runner->next;
}
runner->next = 0;
}
void Rational::deleteMemPool() {
NextOnFreeList *nextPtr;
for(nextPtr = freelist; nextPtr != NULL; nextPtr = freelist) {
freelist = freelist->next;
delete []nextPtr;
}
}
重复性能测试
for(500){
for(1000){
array[1-1000] = new Rational(i);
}
for(1000){
delete array[1-1000];
}
执行时间从1500描述将至43ms。
版本2:固定大小对象的内存池
版本1只可以管理Rational对象,想管理多个对象的话,用模板。因为Rational可以看出,内存管理逻辑是独立于Rational之外的。
template <class T>
class MemoryPool {
public:
MemoryPool (size_t size = EXPANSION_SIZE) ;
~MemoryPool ();
inline void* alloc (size_t size);
inline void free (void *someElement);
private:
MemoryPool<T> *next;
void expandTheFreeList (int howMany = EXPANSION_SIZE);
};
template <class T>
MemoryPool<T>:: MemoryPool (size_t size = EXPANSION_SIZE) {
expandTheFreeList(size);
}
template <class T>
MemoryPool<T>:: ~MemoryPool () {
MemoryPool<T> *nextPtr = next;
for(nextPtr =next; nextPtr != NULL; nextPtr = next) {
next = next->next;
delete []nextPtr;
}
}
template <class T>
inline void* MemoryPool<T>:: alloc (size_t size) {
if(!next) expandTheFreeList();
MemoryPool<T> *head = next;
next = head->next;
return head;
}
template <class T>
inline void MemoryPool<T>:: free (void *doomed) {
MemoryPool<T> *head = static_cast<MemryPool<T> *> doomed;
head->next = next;
next = head;
}
template <class T>
void MemoryPool<T>:: expandTheFreeList (int howMany = EXPANSION_SIZE) {
size_t size = (sizeof(T) > sizeof(MemoryPool<T> *)) ?
sizeof(T) > sizeof(MemoryPool<T> *);
MemoryPool<T> *runner = static_cast< MemoryPool<T> *> new char[size];
next = runner;
for(int i = 0; i < howmany; i++) {
runner->next = static_cast< MemoryPool<T> *> new char[size];
runner = runner->next;
}
runner->next = 0;
}
上述代码解析:与版本1类似,alloc函数负责扩充,free函数负责把T元素放回空闲列表,以此来释放它。
Rational就不需要再维护自己的空闲列表,因此Rational变为
class Rational{
public:
Rational (int a = 0, int b = 1) : n(a), d(b) {}
void* operator new(size_t size) { return memPool->alloc(szie); }
void operator delete (void *doomed, size_t size) { memPool->free(doomed); }
static void newMemPool() { memPool = new MemoryPool<Rational>(); }
static void deleteMemPool() { delete memPool; }
private:
static MemryPool<Rational> *memPool;
int n;
int d;
};
MemoryPool<Rational> *Rational::memPool = 0;
int main(){
Rational *array[1000];
Rational::newMemPool();
for(01-500)
for(1-1000)
array[i] = new Rational(i);
for(1-1000)
delete array[i];
Rational::deleteMemPool();
}
这里的耗时大约是63ms
注意:之前的static_cast的类型转换在编译时可能报错,可以采用强制类型转换
版本3:单线程可变大小的内存管理器
版本2分配的是固定大小的内存,但有些程序需要分配可变大小的内存。
下面是Web服务器中常用的方式:
MemoryChunk类取代了之前版本中使用的NextOnFreeList类,他把不同大小的内存块连接起来形成块序列。
完整代码如下(#####可运行)
#include<iostream>
using namespace std;
#include <string>
class MemoryChunk{
public:
MemoryChunk(MemoryChunk *nextChunk, size_t chunkSize);
~MemoryChunk();
inline void *alloc(size_t size);
inline void free(void* someElement);
MemoryChunk *nextMemChunk(){ return next; }
size_t spaceAvailable(){ return chunkSize - bytesAlreadyAllocated; }
enum {DEFAULT_CHUNK_SIZE = 4096};
private:
MemoryChunk *next;
void *mem;
size_t chunkSize;
size_t bytesAlreadyAllocated;
};
// MemoryChunk是NextOnFreeList的一个更为简洁的版本。将next指针从用于已分配对象的实际内存中分离出来。MemoryChunk使用显式的next和mem指针因此不需要转换。
//MemoryChunk首先确定内存块大小(mem指向),然后next链接下一个MemoryChunk内存块
MemoryChunk::MemoryChunk(MemoryChunk *nextChunk, size_t reqSize){
chunkSize = (reqSize > DEFAULT_CHUNK_SIZE) ? reqSize : DEFAULT_CHUNK_SIZE;
next = nextChunk;
bytesAlreadyAllocated = 0;
mem = new char[chunkSize];
}
MemoryChunk::~MemoryChunk() {
delete[]mem;
}
void* MemoryChunk::alloc(size_t requestSize){
void *addr = (void*)((size_t)mem + bytesAlreadyAllocated);
bytesAlreadyAllocated += requestSize;
return addr;
}
inline void MemoryChunk::free(void *doomed){}//无需担心空闲内存段的释放,对象被删除,内存块也就被释放了
//MemoryChunk只是辅助类,ByteMemoryPool类用它首先可变大小的内存管理。
class ByteMemoryPool{
public:
ByteMemoryPool(size_t initSize = MemoryChunk::DEFAULT_CHUNK_SIZE);
~ByteMemoryPool();
inline void* alloc(size_t size);
inline void free(void* someElement);
private:
MemoryChunk *listOfMemChunks;
void expandStorage(size_t reqSize);
};
//内存块列表可能包含多个块,但只有第一块可用于分配的内存,其他块表示已分配的内存。即列表的首个元素是唯一能够分配可用内存的块。
ByteMemoryPool::ByteMemoryPool(size_t initSize){
expandStorage(initSize);
}
ByteMemoryPool::~ByteMemoryPool(){
MemoryChunk *memChunk = listOfMemChunks;
while (memChunk){
listOfMemChunks = memChunk->nextMemChunk();
delete memChunk;
memChunk = listOfMemChunks;
}
}
//alloc如果确保有足够的可用空间,则会把分配任务托付给列表头的MemoryChunk:
void* ByteMemoryPool::alloc(size_t requestSize){
size_t space = listOfMemChunks->spaceAvailable();
if (space < requestSize){
expandStorage(requestSize); //表示如果当前块的大小不够的话就需要另外扩充新的request大小的MemoryChunk,并把它放在列表头部返回
}
return listOfMemChunks->alloc(requestSize);
}
inline void ByteMemoryPool::free(void *doomed){ listOfMemChunks->free(doomed); }
void ByteMemoryPool::expandStorage(size_t reqSize){
listOfMemChunks = new MemoryChunk(listOfMemChunks, reqSize);
}
//这里需要修改Rational类的实现,用ByteMemoryPool代替MemoryPool<Rational>对象。
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){ memPool->free(doomed); }
static void newMemPool(){
memPool = new ByteMemoryPool();
}
static void deleteMemPool(){ delete memPool; }
private:
static ByteMemoryPool *memPool;
int n, d;
};
ByteMemoryPool* Rational::memPool = nullptr;
int main(){
Rational *array[1000];
Rational::newMemPool();
for (int j = 0; j < 500; j++){
for (int i = 0; i < 1000; i++){
array[i] = new Rational(i);
}
for (int i = 0; i < 1000; i++){
delete array[i];
}
}
Rational::deleteMemPool();
}