zoukankan      html  css  js  c++  java
  • C++中我们为什么提倡使用内存池技术?


    1.什么是内存池技术及为什么要设计自己的内存池?

        通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,
    当频繁使用时会造成大量的内存碎片并进而降低性能。C/C++的内存分配(通过malloc或new)可能需要花费很多时。更糟糕的是,随着时间的流逝,内存(memory)将形成碎片,所以一个应用程序的运行会越来越慢当它运行了很长时间和/或执行了很多的内存分配(释放)操作的时候。特别是,你经常申请很小的一块内存,堆(heap)会变成碎片的,这就是为什么我们经常在运行自己的C/C++程序时一开始还好好的,可是越到后面速度越慢,最后甚至直接罢工了。
        而对于以上,一个可行的的解决方案就是设计一个自己的内存分配策略,也即设计一个你自己的内存池。内存池(Memory Pool)是一种内存分配方式。 内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。在启动的时候,一个”内存池”(Memory Pool)分配一块很大的内存,并将会将这个大块(block)分成较小的块(smaller chunks)。每次你从内存池申请内存空间时,它会从先前已经分配的块(chunks)中得到,而不是从操作系统。
    最大的优势在于:
    1.非常少(几没有) 堆碎片
    2.比通常的内存申请/释放(比如通过malloc, new等)的方式快
    3.检查任何一个指针是否在内存池里
    4.写一个”堆转储(Heap-Dump)”到你的硬盘(对事后的调试非常有用)
    5.某种”内存泄漏检测(memory-leak detection)”:当你没有释放所有以前分配的内存时,内存池(Memory Pool)会抛出一个断言(assertion).

    2.目前有哪些优秀的内存池技术

    1.固定大小缓冲池
    代码很简单,如下:
    template<typename T>
    class CMemoryPool
    {
        public:
            enum { EXPANSION_SIZE = 32};

            CMemoryPool(unsigned int nItemCount = EXPANSION_SIZE)
            {
                ExpandFreeList(nItemCount);
            }
            
            ~CMemoryPool()
            {
                //free all memory in the list
                CMemoryPool<T>* pNext = NULL;
                for(pNext = m_pFreeList; pNext != NULL; pNext = m_pFreeList)
                {
                    m_pFreeList = m_pFreeList->m_pFreeList;
                    delete [](char*)pNext;
                }
            }

            void* Alloc(unsigned int /*size*/)
            {
                if(m_pFreeList == NULL)
                {
                    ExpandFreeList();
                }
                
                //get free memory from head
                CMemoryPool<T>* pHead = m_pFreeList;
                m_pFreeList = m_pFreeList->m_pFreeList;
                return pHead;
            }

            void Free(void* p)
            {
                //push the free memory back to list
                CMemoryPool<T>* pHead = static_cast<CMemoryPool<T>*>(p);
                pHead->m_pFreeList = m_pFreeList;
                m_pFreeList = pHead;
            }

        protected:
            //allocate memory and push to the list
            void ExpandFreeList(unsigned nItemCount = EXPANSION_SIZE)
            {
                unsigned int nSize = sizeof(T) > sizeof(CMemoryPool<T>*) ? sizeof(T) : sizeof(CMemoryPool<T>*);
                CMemoryPool<T>* pLastItem = static_cast<CMemoryPool<T>*>(static_cast<void*>(new char[nSize]));
                m_pFreeList = pLastItem;
                for(int i=0; i<nItemCount-1; ++i)
                {
                    pLastItem->m_pFreeList = static_cast<CMemoryPool<T>*>(static_cast<void*>(new char[nSize]));
                    pLastItem = pLastItem->m_pFreeList;
                }

                pLastItem->m_pFreeList = NULL;
            }

        private:
            CMemoryPool<T>* m_pFreeList;
    };

    它的实现思想就是每次从List的头上取内存, 如果取不到则重新分配一定数量; 用完后把内存放回List头部,这样的话效率很高,因为每次List上可以取到的话,肯定是空闲的内存。当然上面的代码只是针对单线程的,要支持多线程的话也很简单,外面加一层就可以了,
    代码如下:
    class CCriticalSection
    {
    public:
        CCriticalSection()
        {
            InitializeCriticalSection(&m_cs);
        }

        ~CCriticalSection()
        {
            DeleteCriticalSection(&m_cs);
        }

        void Lock()
        {
            EnterCriticalSection(&m_cs);
        }

        void Unlock()
        {
            LeaveCriticalSection(&m_cs);
        }

    protected:
        CRITICAL_SECTION m_cs;
    };

    template<typename POOLTYPE, typename LOCKTYPE>
    class CMTMemoryPool
    {
        public:
            void* Alloc(unsigned int size)
            {
                void* p = NULL;
                m_lock.Lock();
                p = m_pool.Alloc(size);
                m_lock.Unlock();

                return p;
            }

            void Free(void* p)
            {
                m_lock.Lock();
                m_pool.Free(p);
                m_lock.Unlock();    
            }

        private:
            POOLTYPE m_pool;
            LOCKTYPE m_lock;
    };

    2.dlmalloc

        应该来说相当优秀的内存池, 支持大对象和小对象,并且已被广泛使用。到这里下载:ftp://g.oswego.edu/pub/misc/malloc.c关于dlmalloc的内部原理和使用资料可以参考:内存分配器dlmalloc 2.8.3源码浅析.doc


    3.SGI STL 中的内存分配器( allocator )
     SGI STL 的 allocator 应该是目前设计最优秀的 C++ 内存分配器之一了,它的运作原理候捷老师在《 STL 源码剖析》里讲解得非常清楚。基本思路是设计一个 free_list[16]数组,负责管理从 8 bytes 到 128 bytes 不同大小的内存块( chunk ),每一个内存块都由连续的固定大小( fixed size block )的很多 chunk 组成,并用指针链表串接起来。
     比如说free_list[3]->start_notuse->next_notuse->next_notuse->...->end_notuse;当用户要获取此大小的内存时,就在 free_list 的链表找一个最近的 free chunk 回传给用户,同时将此 chunk 从 free_list 里删除,即把此 chunk 前后chunk 指针链结起来。用户使用完释放的时候,则把此chunk 放回到 free_list 中,应该是放到最前面的 start_free 的位置。这样经过若干次 allocator 和 deallocator 后, free_list 中的链表可能并不像初始的时候那么是 chunk 按内存分布位置依次链接的。假如free_list 中不够时, allocator 会自动再分配一块新的较大的内存区块来加入到 free_list 链表中。
     可以自动管理多种不同大小内存块并可以自动增长的内存池,这是 SGI STL 分配器设计的特点。

    4.Loki 中的小对象分配器( small object allocator )
      Loki 的分配器与 SGI STL 的原理类似,不同之处是它管理 free_list 不是固定大小的数组,而是用一个 vector 来实现,因此可以用户指定 fixed size block 的大小,不像 SGI STL 是固定最大 128 bytes 的。另外它管理 free chunks 的方式也不太一样, Loki 是由一列记录了 free block 位置等信息的 Chunk 类的链表来维护的,free blocks 则是分布在另外一个连续的大内存区间中。而且 free Chunks 也可以根据使用情况自动增长和减少合适的数目,避免内存分配得过多或者过少。

    5.Boost 的 object_pool
    Boost 中的 object_pool 也是一个可以根据用户具体应用类的大小来分配内存块的,也是通过维护一个 free nodes 的链表来管理的。可以自动增加 nodes 块,
    初始是 32 个 nodes ,每次增加都以两倍数向 system heap 要内存块。 object_pool 管理的内存块需要在其对象销毁的时候才返还给 system heap


    6.ACE 中的 ACE_Cached_Allocator 和 ACE_Free_List
       ACE 框架中也有一个可以维护固定大小的内存块的分配器,原理与上面讲的内存池都差不多。它是通过在 ACE_Cached_Allocator 中定义个 Free_list 链表来管理一个连续的大内存块的,里面包含很多小的固定大小的未使用的区块( free chunk ),同时还使用 ACE_unbounded_Set 维护一个已使用的 chuncks ,管理方式与上面讲的内存池类似。也可以指定 chunks 的数目,也可以自动增长,定义大致如下所示:

    template<class T>
    class ACE_Cached_Allocator : public ACE_New_Allocator<T> {
    public:
        // Create a cached memory pool with @a n_chunks chunks
        // each with sizeof (TYPE) size.
        ACE_Cached_Allocator(SIZET n_chunks = ACE_DEFAULT_INIT_CHUNKS);
        T* allocate();
        void deallocate(T* p);
    private:
        // List of memory that we have allocated.
        Fast_Unbounded_Set<char *> _allocated_chunks;
        // Maintain a cached memory free list.
        ACE_Cached_Free_List<ACE_Cached_Mem_Pool_Node<T> > _free_list;
    };
    7.TCMalloc
     Google的开源项目gperftools, 主页在这里:https://code.google.com/p/gperftools/,该内存池也被大家广泛好评,并且在google的各种开源项目中被使用,
    比如webkit就用到了它。


  • 相关阅读:
    GCC编绎详解
    GUN C/C++ __attribute__ 用法 转
    rust 参考的资料 转
    Eclipse环境安装rust
    GNU Debugger for Windows----GDB
    minGW cygwin gnuwin32
    tdm-gcc
    GNU tools
    The MinGW and mingw-w64 projects.----GCC
    crosstool-NG
  • 原文地址:https://www.cnblogs.com/ainima/p/6331141.html
Copyright © 2011-2022 走看看