C++实现内存池

多进程编程多用在并发服务器的编写上,当收到一个请求时,服务器新建一个进程处理请求,同时继续监听。为了提高响应速度,服务器采用进程池的方法,在初始化阶段创建一个进程池,池中有许多预创建的进程,当请求到达时,只需从池中分配出来一个进程即可;当进程不够用时,进程池将再次创建一批进程。类似的方法可以用在内存分配上。

C++中,创建一个复杂的对象需要几十条指令,包括函数调用的代价(寄存器值得保存和恢复),以及构造或复制构造函数体的执行代价,甚至动态分配内存的代价。尤其是,在不重载new和delete运算符时,由于C++的new和delete是通用的,其中甚至提供了支持多线程编程的同步机制,对于单线程编程将带来不必要的运算。

如果需要创建的对象的个数是动态变化的,可以首先预开辟一片较大的内存,每次要创建对象时将一段内存分配出去。为了提高创建对象的速度,内存池应满足如下设计要求:

1,一定的通用性,适用于多类型,而不局限于某个类;

2,一定的灵活性,可以在不对类作过多修改的前提下,将内存池机制轻易融入到原代码中;

3,可能要满足多线程的要求。

4,通用性、灵活性视实际情况而定,不必完美。

一、专用内存池

先从专用内存池开始,假设要不断创建Complex (复数)类,下面的代码实现了针对Complex对象的内存池。代码最初来自《Effective C++》,笔者在看过书以后自行编写而成。

#include <iostream>
#include <time.h>
using namespace std;

class SimpleComplex
{
    double re;
    double im;

public:
    explicit SimpleComplex(const double r = 0.0, const double i = 0.0):re(r), im(i) {}

};

class Next
{
public:
    Next * next;
};

class Complex
{
    static Next * freelist;
    enum {EXPAND_SIZE = 2};

    double re;
    double im;

    static void expand()
    {
        Next * p = (Next *) (new char [sizeof(Complex)]);
        freelist = p;
        for(int i=1; i<EXPAND_SIZE; i++)
        {
            p->next = (Next *) new char [sizeof(Complex)];
            p = p->next;
        }
        p->next = 0;
    }

public:
    explicit Complex(const double r = 0.0, const double i = 0.0):re(r), im(i) {}
    inline void * operator new (size_t size)
    {
        if (0 == freelist)
            expand();
        Next * p = freelist;
        freelist = freelist->next;
        return p;
    }
    inline void operator delete(void * ptr, size_t size)
    {
        ((Next *)ptr)->next = freelist;
        freelist = (Next *)ptr;
    }
    static void newPool()
    {
        expand();
    }
    static void deletePool()
    {
        Next * p = freelist;
        while (p)
        {
            freelist = freelist->next;
            delete [] p;
            p = freelist;
        }
    }
};

Next * Complex::freelist = 0;

main()
{
    double from = time(0);
    Complex *c[100];
    for (int i=0; i<1000000; i++)
    {
        for (int j=0; j<100; j++)
            c[j] = new Complex();
        for (int j=0; j<100; j++)
            delete c[j];
    }
    Complex::deletePool();
    double to = time(0);
    cout <<to-from<<endl;

    from = time(0);
    SimpleComplex *s[100];
    for (int i=0; i<1000000; i++)
    {
        for (int j=0; j<100; j++)
            s[j] = new SimpleComplex();
        for (int j=0; j<100; j++)
            delete s[j];
    }
    to = time(0);
    cout <<to-from<<endl;
}

注意虽然有一个class next,但总体上看所有的内存块却并不组成链表。这里,内存是这么分配的:任何时刻freelist指向可用的内存块链表的头部,即第一个可用的内存块(单不足时,freelist指向NULL)。假设分别执行下面的语句:

p1 = new Complex ();
p2 = new Complex ();
p3 = new Complex ();
p4 = new Complex ();
delete p2;
delete p4;
p5 = new Complex ();

过程是这样的。Complex专属内存池在初始时刻不分配任何空间。给p1创建对象时,由于freelist指向NULL,先按照EXPAND_SIZE的值,开辟EXPAND_SIZE * sizeof(Complex)大小的内存。此时:

freelist -> [      next] -> [     next] -> NULL

随后将freelist指向的内存块分配给p1:

p1-> [      next]       freelist-> [     next] -> NULL

随后将freelist指向的内存块分配给p2:

p1-> [      next]       p2-> [     next]      freelist -> NULL

随后为p3分配内存,在此之前再次执行expand() :

p1-> [      next]       p2 -> [     next]     p3 -> [    next]     freelist -> [    next] -> NULL

随后将freelist指向的内存块分配给p4:

p1-> [      next]       p2 -> [     next]     p3 -> [    next]     p4 -> [    next]      freelist-> NULL

随后释放p2,内存池这时需要将p2的空间回收再利用,于是把p2的空间插入到freelist指向的链的前端:

p1-> [      next]       p3 -> [    next]      p4 -> [    next]     freelist -> [(p2) next]   -> NULL

随后释放p2,内存池这时需要将p2的空间回收再利用,于是把p2的空间插入到freelist指向的链的前端:

p1-> [      next]       p3 -> [    next]     freelist -> [(p4) next] -> [(p2) next]   -> NULL

这样为p5开辟内存时,只需将原p4的内存给p5即可。

虽然池中的内存块不是以链表形式存在,当上述代码保证每个内存块都有一个指针变量指向它,最终在销毁时可以正常销毁。当然,如果仅仅写道

new Complex ();

这时创建的内存块就成了碎片,就算对于通用的new delete操作符而言,这样的碎片也是无可奈何的。注意,内存池并不实现像Java和C#这样自动回收内存的机制。

2, 通用固定大小内存池

上述内存池的实现嵌入在Complex代码中,只具备最低级的代码重用性(即复制粘贴方式的重用)。下面这个内存池通过模板的方式,适用于所有的单一类型。

#include <stdio.h>
#include <time.h>

class MemPoolNext
{
    public: MemPoolNext * next;
};

template <class T>
class MemPool
{
    enum {EXPAND_SIZE = 16};
    static MemPoolNext * freelist;
    static void expand()
    {
        size_t size = sizeof(T);
        if(size < sizeof(void *))
            size = sizeof(void *);
        MemPoolNext * p = (MemPoolNext *) new char [size];
        freelist = p;
        for(int i=1; i<EXPAND_SIZE; i++, p=p->next)
            p->next = (MemPoolNext *) new char [size];
        p->next = 0;
    }
public:
    static void * alloc (size_t)
    {
        if(0 == freelist)
            expand();
        void * p = freelist;
        freelist = freelist->next;
        return p;
    }
    static void free (void * p, size_t)
    {
        ((MemPoolNext *) p)->next = freelist;
        freelist = (MemPoolNext *) p;
    }
    static void deletepool()
    {
        MemPoolNext * p = freelist;
        while( p!=0 )
        {
            freelist = p->next;
            delete p;
            p = freelist;
        }
    }
};
template <class T>
MemPoolNext * MemPool<T>::freelist = 0;

class Complex
{
public:
    double re;
    double im;
    explicit Complex(double r = 0., double i = 0.): re(r), im(i){}
    inline static void * operator new (size_t s)
    {
        return MemPool<Complex>::alloc(s);
    }
    inline static void operator delete (void * p, size_t s)
    {
        MemPool<Complex>::free(p, s);
    }
    inline static void deletepool()
    {
        MemPool<Complex>::deletepool();
    }
};

main()
{
    double from = time(0);
    Complex * c[100];
    for (int i=0; i<1000000; i++)
    {
        for(int j=0; j<100; j++)
            c[j] = new Complex(j, j);
        for(int j=0; j<100; j++)
            delete c[j];
    }
    Complex::deletepool();
    double to = time(0);
    printf("%f\n", to-from);
}

实际上很简单,无非是将原来写在Complex中用来管理内存的代码封装成了一个新的类MemPool。

三,多线程固定大小内存池

代码略,只需在临界区前后加上锁即可,最常用的锁是pthread_mutex_lock(信号量锁)。

四,多线程非固定大小内存池

此时,一个内存池内的对象已不局限于单一的类,而是能够同时包容不同类型的对象。代码略,只需在开辟和回收内存时,考虑当前可用的内存大小和已分配的内存大小即可。

时间: 2024-09-28 21:10:41

C++实现内存池的相关文章

内存池、进程池、线程池

首先介绍一个概念"池化技术 ".池化技术 一言以蔽之就是:提前保存大量的资源,以备不时之需以及重复使用. 池化技术应用广泛,如内存池,线程池,连接池等等.内存池相关的内容,建议看看Apache.Nginx等开源web服务器的内存池实现. 起因:由于在实际应用当中,分配内存.创建进程.线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作.           因此,当程序中需要频繁的进行内存申请释放,进程.线程创建销毁等操作时,通常会使用内存池.进程池.

详谈内存管理技术(二)、内存池

嗯,这篇讲可用的多线程内存池. 零.上期彩蛋:不要重载全局new 或许,是一次很不愉快的经历,所以在会有这么一个"认识".反正,大概就是:即使你足够聪明,也不要自作聪明:在这就是不要重载全局new,无论你有着怎样的目的和智商.因为: class XXX{ public: XXX* createInstance(); }; 这是一个不对称的接口:只告诉了我们如何创建一个[堆]对象,但是释放呢??! 很无奈,只能假设其使用全局默认的delete来删除(除此之外,没有其他选择):这时,我为了

InnoDB 存储引擎的线程与内存池

InnoDB 存储引擎的线程与内存池 InnoDB体系结构如下: 后台线程: 1.后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据: 2.另外,将以修改的数据文件刷新到磁盘文件: 3.同时,保证在数据库发生异常的情况下,InnoDB能恢复到正常运行状态. 内存池:InnoDB有多个内存块,这些内存块组成了一个大的内存池.这些内存块包括有:缓冲池(innodb_buffer_pool)和日志缓冲(log_buffer)以及额外内存池(innodb_addtional

内存池技术介绍(图文并茂,非常清楚)

看到一篇关于内存池技术的介绍文章,受益匪浅,转贴至此. 原贴地址:http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html 6.1 自定义内存池性能优化的原理 如前所述,读者已经了解到"堆"和"栈"的区别.而在编程实践中,不可避免地要大量用到堆上的内存.例如在程序中维护一个链表的数据结构时,每次新增或者删除一个链表的节点,都需要从内存堆上分配或者释放一定的内存:在维护一个动态数组时,如果动态数组的

基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): 1.实现基本通信框架,包括对游戏的需求分析.设计及开发环境和通信框架的搭建: 2.实现网络底层操作,包括创建线程池.序列化网络包等: 3.实战演练,实现类似于CS反恐精英的3D对战网络游戏: 技术要点:C++面向对象思想.网络编程.Qt界面开发.Qt控件知识.Boost智能指针.STL算法.STL.

重写boost内存池

最近在写游戏服务器网络模块的时候,需要用到内存池.大量玩家通过tcp连接到服务器,通过大量的消息包与服务器进行交互.因此要给每个tcp分配收发两块缓冲区.那么这缓冲区多大呢?通常游戏操作的消息包都很小,大概几十字节.但是在玩家登录时或者卡牌游戏发战报(将整场战斗打完,生成一个消息包),包的大小可能达到30k或者更大,取决于游戏设定.这些缓冲区不可能使用glibc原始的new.delete来分配,这样可能会造成严重的内存碎片,并且效率也不高. 于是我们要使用内存池.并且是等长内存池,即每次分配的内

Innodb额外内存池的分配策略以及性能

Innodb额外内存池的分配策略以及性能 作者:明天会更好 QQ:715169549 备注:未经同意,严禁转载,谢谢合作. //内存池结构体 /** Data structure for a memory pool. The space is allocated using the buddy algorithm, where free list i contains areas of size 2 to power i. */ struct mem_pool_t{ byte* buf; /*!

[原创]loki库之内存池SmallObj

loki库之内存池SmallObj 介绍 loki库的内存池实现主要在文件smallobj中,顾名思义它的优势主要在小对象的分配与释放上,loki库是基于策略的方法实现的,简单的说就是把某个类通过模板参数传递给主类,比如某个对象的创建可以通过不同的创建策略进行创建,本文主要讲loki的大致实现. smallobj层次 loki.smallobj主要分四层: 应用层smallobject,重载了operator new 和operator delete,内存通过底层获取 内存分配smallobjA

【核心基础】内存池

本节将研究Nginx关于内存申请与释放的核心代码: 基本示意图 内存池对象初始状态 小内存申请后状态 大内存申请后状态 核心代码分析 核心结构体声明 //大内存管理结构 struct ngx_pool_large_s { ngx_pool_large_t *next; //连接下一个大内存管理 void *alloc; //申请的大内存地址 }; //内存池中数据管理 typedef struct { u_char *last; //可用内存的起始地址 u_char *end; //可用内存的末

简单的内存池实现gko_alloc

在用gpreftools优化gko_pool的时候我发现一个问题,malloc竟然成了性能瓶颈 由于在每个连接建立的时候gko_pool默认会为读写各分配2KB的buf备用,这个是比较固定的 每个连接的的生命周期会伴随着4KB大小的内存malloc & free 正好可以写个只能分配固定大小内存的"内存池",基本思路就是每次分配一个大内存bucket(64MB),需要4KB的块的时候就从bucket中取,当bucket没有可用slot就再分配一个新的bucket,当bucket