zoukankan      html  css  js  c++  java
  • C++实现简单的内存池

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

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

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

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

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

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

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

    一、专用内存池

        先从专用内存池开始,假设要不断创建Complex (复数)类,下面的代码实现了针对Complex对象的内存池。看过《Efficient C++》(不是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#这样自动回收内存的复杂机制,毕竟像JVM这样以插入一层虚拟机并牺牲一定的性能为代价,召唤出“上帝视角”后,什么事都能干成,因为一切的一切都被一个上帝管着,区区未指向的内存块的回收就不在话下了,你找不到它,上帝找得到。“BB is watching you!”(老大哥在看着你!) 

    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
    ", to-from);
    }

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

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

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

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

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

  • 相关阅读:
    (单例)使用同步基元变量来检测程序是否已运行
    使用委托解决方法的跨线程调用问题
    Rtmp/Hls直播、点播服务器部署与配置
    关于C#调用广州医保HG_Interface.dll调用的一些总结(外部组件异常)
    redhat7.3配置163 yum源
    模块化InnoSetup依赖项安装
    [迷宫中的算法实践]迷宫生成算法——递归分割算法
    [新手学Java]使用beanUtils控制javabean
    【HTML5】Canvas绘图详解-1
    【Swift 】- 闭包
  • 原文地址:https://www.cnblogs.com/zhchngzng/p/4013113.html
Copyright © 2011-2022 走看看