• 固定内存块尺寸的内存池原理及代码


    有些情形会需要申请大量的固定尺寸的内存块,若一个个都用malloc申请效率很低,这种情况非常适合使用内存池解决。

    下面就是一个固定内存块尺寸的内存池的完整源码。注:其中的内存申请不是使用的malloc,而是自己定义的torch::HeapMalloc,简单修改下即可。

    代码详情请见Github【点击】

    先看代码再看讲解

    /*
     * 固定尺寸的内存池
     * 说明:
     *  - 只适用于需要大量固定尺寸内存块的情况
     */
    template<size_t _Size>
    class FixedMemoryPool
    {
    public:
        enum { _CHUNK_COUNT = 1024/_Size };
    
        FixedMemoryPool();
        ~FixedMemoryPool();
    
        FixedMemoryPool(const FixedMemoryPool &);
        FixedMemoryPool& operator=(const FixedMemoryPool &) = delete;
    
        /*
         * 从内存池获得一个内存块
         * 注意:
         *  - 用完后最好在放回内存池中(有利于提高性能)
         *  - 不能使用::free()释放内存,内存池会统一释放池中的内存块
         */
        void* Alloc();
        
        /*
         * 将内存块放回内存池中
         * 注意:必须是从内存池申请的内存块才能放回去
         */
        void Free(void *mem);
    
        /*
         * 清空内存池
         * 注意:调用此接口后,通过Alloc申请的内存块会全部被释放
         */
        void Clear();
    
        /*
         * 获得内存池总大小(单位Byte)
         */
        size_t GetCapacity();
    
        /*
         * 获得内存块的大小(单位Byte)
         */
        size_t GetItemSize();
    
    private:
        struct Chunk;
        Chunk* MakeChunk();
    
    private:
        union ChunkItem {
            ChunkItem*  next;
            uint8_t     mem[_Size];
        };
        struct Chunk { ChunkItem itemArray[_CHUNK_COUNT]; };
    
        ChunkItem*          m_rootItem;
        std::list<Chunk *>  m_chunkList;
    };
    
    template<size_t _Size>
    FixedMemoryPool<_Size>::FixedMemoryPool() {
        m_rootItem = nullptr;
        Chunk *chunk = this->MakeChunk();
        if (!chunk)
            return;
        m_chunkList.push_back(chunk);
        m_rootItem = chunk->itemArray;
    }
    
    template<size_t _Size>
    FixedMemoryPool<_Size>::~FixedMemoryPool() {
        this->Clear();
    }
    
    template<size_t _Size>
    FixedMemoryPool<_Size>::FixedMemoryPool(const FixedMemoryPool &) {
        m_rootItem = nullptr;
        Chunk *chunk = this->MakeChunk();
        if (!chunk)
            return;
        m_chunkList.push_back(chunk);
        m_rootItem = chunk->itemArray;
    }
    
    template<size_t _Size>
    void* FixedMemoryPool<_Size>::Alloc() {
        if (!m_rootItem) {
            Chunk *chunk = this->MakeChunk();
            if (!chunk) return nullptr;
            m_chunkList.push_back(chunk);
            m_rootItem = chunk->itemArray;
        }
        void *item = m_rootItem;
        m_rootItem = m_rootItem->next;
        memset(item, 0, _Size);
        return item;
    }
    
    template<size_t _Size>
    void FixedMemoryPool<_Size>::Free(void *mem) {
        if (!mem) return;
        ChunkItem *item = static_cast<ChunkItem*>(mem);
        item->next = m_rootItem;
        m_rootItem = item;
    }
    
    template<size_t _Size>
    void FixedMemoryPool<_Size>::Clear() {
        for (Chunk *chunk : m_chunkList) {
            torch::HeapFree(chunk);
        }
        m_chunkList.clear();
    }
    
    template<size_t _Size>
    size_t FixedMemoryPool<_Size>::GetCapacity() {
        return m_chunkList.size() * _CHUNK_COUNT * _Size;
    }
    
    template<size_t _Size>
    size_t FixedMemoryPool<_Size>::GetItemSize() {
        return _Size;
    }
    
    template<size_t _Size>
    typename FixedMemoryPool<_Size>::Chunk* FixedMemoryPool<_Size>::MakeChunk() {
        Chunk *chunk = (Chunk *)torch::HeapMalloc(sizeof(Chunk));
        if (!chunk)
            return nullptr;
        for (int i = 0; i < _CHUNK_COUNT - 1; i++) {
            chunk->itemArray[i].next = &(chunk->itemArray[i+1]);
        }
        chunk->itemArray[_CHUNK_COUNT-1].next = nullptr;
        return chunk;
    }

    首先要清除这个内存池的结构:

    ChunkList[
        Chunk[ChunkItem, ... ChunkItem],
        Chunk[ChunkItem, ... ChunkItem],
        ...
        Chunk[ChunkItem, ... ChunkItem]
    ]

    ChunkItem非常重要,他是一个union,其中有两个成员:

        union ChunkItem {
            ChunkItem*  next; // 指向下一个ChunkItem
            uint8_t     mem[_Size]; // _Size为模板参数
        };
      
    struct Chunk { ChunkItem itemArray[_CHUNK_COUNT]; }; 

    当ChunkItem在池中时,只会使用next域,使用next将一个Chunk中的所有ChunkItem串联成一个链表。

    到这里有人发现了,Chunk里面不是一个ChunkItem的数组吗,干毛还用next串联成链表。

    我想说的是,此中有深意呀。首先这样更加灵活,当然主要还和内存块的回收有关系,内存块回收回来时这个ChunkItem的位置不确定(在数组中的位置,处于哪一个Chunk),所以直接将他串到链表头就可以了,没必要关心。所以这个Chunk只是刚初始化时是数组和链表等同,后面经过Alloc和Free后,链表和数组就不等同了。

    mem字段比较简单了,由末班参数_Size控制其大小,一般也就是ChunkItem的大小(_Size若大于4),ChunItem本身就是分配给用户使用的内存块,尺寸也是用户通过模板参数传入的_Size


        ChunkItem*          m_rootItem;
        std::list<Chunk *>  m_chunkList;

    先说m_rootItem,其实就是指向可用的ChunkItem的指针,当Alloc申请时就可以非常简单的

        void *item = m_rootItem;
        m_rootItem = m_rootItem->next;
        memset(item, 0, _Size);
        return item;

    m_chunkList,其实就是Chunk的记录list,主要用于内存释放,因为一个Chunk的内存块数固定,所以当内存块被申请完之后,就重新构造一个Chunk出来,然后将所有Chunk都保存到m_chunkList。这样就可以:

        for (Chunk *chunk : m_chunkList) {
            torch::HeapFree(chunk);
        }
        m_chunkList.clear();

    下面看看比较关键的Alloc的实现:

    template<size_t _Size>
    void* FixedMemoryPool<_Size>::Alloc() {
        if (!m_rootItem) { // 当m_rootItem为空,代表没有可用的内存块了。 此时需要重新构造一个Chunk出来
            Chunk *chunk = this->MakeChunk(); // 构造一个Chunk出来,详情见下面
            if (!chunk) return nullptr;
            m_chunkList.push_back(chunk); // 将新构造的Chunk,记录到list中,否则会造成内存泄露
            m_rootItem = chunk->itemArray; // 让m_rootItem指向,新构造的Chunk的ItemArray中的第一个元素
        }
        // 直接将m_rootItem返回,然后将m_rootItem指向下一个
        void *item = m_rootItem;
        m_rootItem = m_rootItem->next;
        memset(item, 0, _Size);
        return item;
    }

    MakeChunk的细节:

    template<size_t _Size>
    typename FixedMemoryPool<_Size>::Chunk* FixedMemoryPool<_Size>::MakeChunk() {
        Chunk *chunk = (Chunk *)torch::HeapMalloc(sizeof(Chunk)); // 申请一个Chunk的内存块,struct Chunk {ChunkItem itemArray[_CHUNK_COUNT];};
        if (!chunk)
            return nullptr;
        for (int i = 0; i < _CHUNK_COUNT - 1; i++) { // 链表结构的初始化,将头一个指向下一个ChunkItem
            chunk->itemArray[i].next = &(chunk->itemArray[i+1]);
        }
        chunk->itemArray[_CHUNK_COUNT-1].next = nullptr; // 注意!!!最后一个一定要指向null,这样最后一个被申请后,m_rootItem就变为null了
        return chunk;
    }

      

    Free比较简单:

    template<size_t _Size>
    void FixedMemoryPool<_Size>::Free(void *mem) {
        if (!mem) return;
        ChunkItem *item = static_cast<ChunkItem*>(mem); 
        item->next = m_rootItem; // 直接将内存块放到链表头
        m_rootItem = item;
    }

    固定尺寸的内存池实现并不复杂,但是却非常有用,Alloc和Free都只是移动一个指针就可以完成内存的申请和释放(在不触发MakeChunk的情况下),非常高效。

    这里只介绍了内存池的核心算法,其他地方都比较简单。

  • 相关阅读:
    C#获取IP信息
    获取百度地图按条件查找的信息
    react中如何实现一个按钮的动态隐藏和显示(有效和失效)
    es6 关于map和for of的区别有哪些?
    js 高级程序设计 第四章学习笔记
    ant design Table合并单元格合并单元格怎么用?
    React中如何实现模态框每次打开都是初始界面
    前端界面布局相关整理之2017
    待改善的代码整理
    cq三期备注说明
  • 原文地址:https://www.cnblogs.com/luweimy/p/5172875.html
Copyright © 2020-2023  润新知