C++程序设计内存管理器(内存池)

一、New/malloc内存分配很慢


C语言中的标准库函数malloc、free、calloc和realloc,以及C++中的new、new[]、delete和delete[] 操作符,是这两种语言中内存管理的关键之处。(注意一下C语言里面的是函数,在C++里面是操作符,这个据说在面试里面被问到过。)
在执行时,malloc和new将向操作系统内核请求内存,而free和delete则请求释放内存。这意味着,操作系统必须在每次提出内存请求时在用户空间代码和内核代码之间进行切换。这种切换直接导致了运行效率的下降。

在VS2012中测试如下代码:


#include <ctime>
#include <iostream>
using namespace std;

class Complex
{
public:
	Complex(double a, double b) : r(a),c(b)
	{
	}
private:
	double r;				//实部
	double c;				//虚部
};

int main(int argc, _TCHAR* argv[])
{
	Complex* array[1000];
	clock_t t1,t2;
	t1 = clock();
	for (int i = 0; i < 5000; i++)
	{
		for (int j = 0; j < 1000; j++)
		{
			array[j] = new Complex(i,j);
		}
		for (int j=0;j<1000;j++)
		{
			delete array[j];
		}
	}
	t2 = clock();
	cout << t2-t1 << "ms" << endl;
	return 0;
}
考虑到VS2010中Debug版本和Release版本运行效率的差异,分别使用Debug模式和Release模式编译运行程序,得到结果:

debug模式下:

Release模式下:

Release模式下,程序的运行效率是要远远高于debug模式的,这是因为debug模式中有很多调试信息,而且没有对程序进行优化。


二、优化内存管理

    

STL中vector容器,在当前容器空间不足时,总是以两倍的方式去分配空间。其主要的问题也是在于使用new操作符或者malloc函数向系统申请内存这一过程是比较耗时。照此看可以采取先多分配一些空间,这样就可以减少使用new和malloc的次数,相应的客户代码与系统内核间的切换次数就大大减少了。根据IBM的一篇文章(简单的内存池办法,文章在博客结尾提供下载地址),按照其思路来优化我们上述的代码。
流程如下:
1、考虑在程序开始运行的时候就去申请一段内存空间,我们定义一个全局的对象,通过这个对象的构造函数来完成初始内存的申请工作。
假设该全局对象定义如下:
MemoryManager gMemoryManager;
2、对我们已经写好的类进行new,new[],delete,delete[]的重载,是我们在调用这些操作符的时候转而去调用上一步中全局对象,让该全局对象给我们提供空间。这样,改写上述的Complex类:

class Complex
{
public:
    Complex (double a, double b): r (a), c (b) {}
	//重载new操作符
    inline void* operator new(size_t size)
    {
        return gMemoryManager.allocate(size);
    }

	//重载delete操作符
    inline void  operator delete(void* object)
    {
        gMemoryManager.free(object);
    }
	
	//重载new[]操作符
    inline void* operator new [](size_t size)
    {
        return gMemoryManager.allocate(size);
    }

	//重载delete[]操作符
    inline void  operator delete [](void* object)
    {
        gMemoryManager.free(object);
    }
    private:
        double r; 	// Real Part
        double c; 	// Complex Part
};

那么,如何来对这个全局对象进行抽象也就成了目前的焦点问题。从外部调用它来看,它应该具有两个接口,一个是提供一个allocate,用于类申请时的空间供给。另一个就是free,用于外部访问时候空间的释放。
从该类的内部来看,它主要的功能应该在于内存的申请和释放。


//设计该接口在此案例中并没有突出的作用
class IMemoryManager
{
public:
    virtual void* allocate(size_t) = 0;
    virtual void   free(void*) = 0;
};


class MemoryManager : public IMemoryManager
{
private:
	struct FreeStore
	{
		FreeStore *next;
	};
	void ExpandPoolSize();		//扩展内存空间
	void CleanUp();				//清理内存
	FreeStore* freestorehead;
public:
    MemoryManager()
	{
		freestorehead = NULL;
		ExpandPoolSize();
	}
    ~MemoryManager()
	{
		CleanUp();
	}
    void* allocate(size_t);
    void  free(void*);
};


//程序启动时,内存池默认的空间大小
const int POOLSIZE = 128;


//提供给外部访问
void* MemoryManager::allocate(size_t size)
{
	if (0 == freestorehead)
		ExpandPoolSize();
	FreeStore *head = freestorehead;
	freestorehead = head->next;
	return head;
}


//提供给外部访问
void MemoryManager::free(void* object)
{
	FreeStore* head = static_cast<FreeStore*>(object);
	head->next = freestorehead;
	freestorehead = head;
}


//内部访问,扩充内存
void MemoryManager::ExpandPoolSize()
{
	size_t size = (sizeof(Complex) > sizeof(FreeStore*)) ? sizeof(Complex) : sizeof(FreeStore*);
	FreeStore* head = reinterpret_cast<FreeStore*>(new char[size]);
	freestorehead = head;


	for (int i = 0; i < POOLSIZE; i++)
	{
		head->next = reinterpret_cast<FreeStore*>(new char[size]);
		head = head->next;
	}
	head->next = 0;
}


//清理内存
void MemoryManager::CleanUp()
{
	FreeStore* nextPtr = freestorehead;
	for (; nextPtr; nextPtr = freestorehead)
	{
		nextPtr = freestorehead;
		freestorehead = freestorehead->next;
		delete []nextPtr;
	}
}

按照上述代码,重新测试最初的那个程序会发现运行时间会大大的缩短。下面两图分别是在debug和Release模式下测得的结果:




上述过程的完整代码如下:

memoty.h

#ifndef __MEMORY_H__
#define __MEMORY_H__

class IMemoryManager
{
    public:
    virtual void* allocate(size_t) = 0;
    virtual void   free(void*) = 0;
};

class MemoryManager : public IMemoryManager
{
	struct FreeStore
	{
		FreeStore *next;
	};
	void ExpandPoolSize();		//扩展内存空间
	void CleanUp();				//清理内存
	FreeStore* freestorehead;
public:
    MemoryManager()
	{
		freestorehead = NULL;
		ExpandPoolSize();
	}
    ~MemoryManager()
	{
		CleanUp();
	}
    void* allocate(size_t);
    void  free(void*);
};




#endif //

memory.cpp

#include <iostream>
using namespace std;

#include "memory.h"
#include "comlpex.h"

//全局的内存管理对象
MemoryManager gMemoryManager;

//程序启动时,内存池默认的空间大小
const int POOLSIZE = 128;

//提供给外部访问
void* MemoryManager::allocate(size_t size)
{
	if (0 == freestorehead)
		ExpandPoolSize();
	FreeStore *head = freestorehead;
	freestorehead = head->next;
	return head;
}

//提供给外部访问
void MemoryManager::free(void* object)
{
	FreeStore* head = static_cast<FreeStore*>(object);
	head->next = freestorehead;
	freestorehead = head;
}

//内部访问,扩充内存
void MemoryManager::ExpandPoolSize()
{
	size_t size = (sizeof(Complex) > sizeof(FreeStore*)) ? sizeof(Complex) : sizeof(FreeStore*);
	FreeStore* head = reinterpret_cast<FreeStore*>(new char[size]);
	freestorehead = head;

	for (int i = 0; i < POOLSIZE; i++)
	{
		head->next = reinterpret_cast<FreeStore*>(new char[size]);
		head = head->next;
	}
	head->next = 0;
}

//清理内存
void MemoryManager::CleanUp()
{
	FreeStore* nextPtr = freestorehead;
	for (; nextPtr; nextPtr = freestorehead)
	{
		nextPtr = freestorehead;
		freestorehead = freestorehead->next;
		delete []nextPtr;
	}
}


main.cpp

#include <iostream>
#include <string>
using namespace std;

#include "memory.h"
#include "comlpex.h"
#include <ctime>

int main()
{
    Complex* array[1000];
	clock_t t1, t2;
	t1 = clock();
    for (int i = 0;i < 5000; i++)
    {
        for (int j = 0; j < 1000; j++)
        {
            array[j] = new Complex (i, j);
        }
        for (int j = 0; j < 1000; j++)
        {
            delete array[j];
        }
    }
	t2 = clock();
	cout<<t2-t1<<"ms"<<endl;
    return 0;
}


文章下载






你可能感兴趣的:(C++,malloc,vs2012)