您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > C/C++/C#

C++内存池的简单原理及实现(纯代码解析)

时间:2021-11-17 11:09:33  来源:  作者:深度Linux

一、为什么需要使用内存

在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。

一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;

另一方面,频繁的分配和释放小块内存会导致大量的内存碎片的产生,当碎片积累到一定的量之后,将无法分配到连续的内存空间,系统不得不进行碎片整理来满足分配到连续的空间,这样不仅会导致系统性能损耗,而且会导致程序对内存的利用率低下。

当然,如果我们的程序不需要频繁的分配和释放小块内存,那就没有使用内存池的必要,直接使用malloc,free或new,delete函数即可。

二、内存池的实现方案

内存池的实现原理大致如下:

提前申请一块大内存由内存池自己管理,并分成小片供给程序使用。程序使用完之后将内存归还到内存池中(并没有真正的从系统释放),当程序再次从内存池中请求内存时,内存池将池子中的可用内存片返回给程序使用。

我们在设计内存池的实现方案时,需要考虑到以下问题:

内存池是否可以自动增长?

如果内存池的最大空间是固定的(也就是非自动增长),那么当内存池中的内存被请求完之后,程序就无法再次从内存池请求到内存。所以需要根据程序对内存的实际使用情况来确定是否需要自动增长。

内存池的总内存占用是否只增不减?

如果内存池是自动增长的,就涉及到了“内存池的总内存占用是否是只增不减”这个问题了。试想,程序从一个自动增长的内存池中请求了1000个大小为100KB的内存片,并在使用完之后全部归还给了内存池,而且假设程序之后的逻辑最多之后请求10个100KB的内存片,那么该内存池中的900个100KB的内存片就一直处于闲置状态,程序的内存占用就一直不会降下来。对内存占用大小有要求的程序需要考虑到这一点。

内存池中内存片的大小是否固定?

如果每次从内存池中的请求的内存片的大小如果不固定,那么内存池中的每个可用内存片的大小就不一致,程序再次请求内存片的时候,内存池就需要在“匹配最佳大小的内存片”和“匹配操作时间”上作出衡量。“最佳大小的内存片”虽然可以减少内存的浪费,但可能会导致“匹配时间”变长。

内存池是否是线程安全的?

是否允许在多个线程中同时从同一个内存池中请求和归还内存片?这个线程安全可以由内存池来实现,也可以由使用者来保证。

内存片分配出去之前和归还到内存池之后,其中的内容是否需要被清除?

程序可能出现将内存片归还给内存池之后,仍然使用内存片的地址指针进行内存读写操作,这样就会导致不可预期的结果。将内容清零只能尽量的(也不一定能)将问题抛出来,但并不能解决任何问题,而且将内容清零会消耗一定的CPU时间。所以,最终最好还是需要由内存池的使用者来保证这种安全性。

是否兼容std::allocator?

STL标准库中的大多类都支持用户提供一个自定义的内存分配器,默认使用的是std::allocator,如std::string:

typedef basic_string<char, char_traits<char>, allocator<char> > string;

如果我们的内存池兼容std::allocator,那么我们就可以使用我们自己的内存池来替换默认的std::allocator分配器,如:

typedef basic_string<char, char_traits<char>, MemoryPoll<char> > mystring;

 

三、内存池的具体实现

计划实现一个内存池管理的类MemoryPool,它具有如下特性:

  1. 内存池的总大小自动增长。
  2. 内存池中内存片的大小固定。
  3. 支持线程安全。
  4. 在内存片被归还之后,清除其中的内容。
  5. 兼容std::allocator。

因为内存池的内存片的大小是固定的,不涉及到需要匹配最合适大小的内存片,由于会频繁的进行插入、移除的操作,但查找比较少,故选用链表数据结构来管理内存池中的内存片。

MemoryPool中有2个链表,它们都是双向链表(设计成双向链表主要是为了在移除指定元素时,能够快速定位该元素的前后元素,从而在该元素被移除后,将其前后元素连接起来,保证链表的完整性):

  1. data_element_ 记录以及分配出去的内存片。
  2. free_element_ 记录未被分配出去的内存片。

MemoryPool实现代码

代码中使用了std::mutex等C++11才支持的特性,所以需要编译器最低支持C++11:

#ifndef PPX_BASE_MEMORY_POOL_H_
#define PPX_BASE_MEMORY_POOL_H_

#include <climits>
#include <cstddef>
#include <mutex>

namespace ppx {
    namespace base {
        template <typename T, size_t BlockSize = 4096, bool ZeroOnDeallocate = true>
        class MemoryPool {
        public:
            /* Member types */
            typedef T               value_type;
            typedef T*              pointer;
            typedef T&              reference;
            typedef const T*        const_pointer;
            typedef const T&        const_reference;
            typedef size_t          size_type;
            typedef ptrdiff_t       difference_type;
            typedef std::false_type propagate_on_container_copy_assignment;
            typedef std::true_type  propagate_on_container_move_assignment;
            typedef std::true_type  propagate_on_container_swap;

            template <typename U> struct rebind {
                typedef MemoryPool<U> other;
            };

            /* Member functions */
            MemoryPool() noexcept;
            MemoryPool(const MemoryPool& memoryPool) noexcept;
            MemoryPool(MemoryPool&& memoryPool) noexcept;
            template <class U> MemoryPool(const MemoryPool<U>& memoryPool) noexcept;

            ~MemoryPool() noexcept;

            MemoryPool& operator=(const MemoryPool& memoryPool) = delete;
            MemoryPool& operator=(MemoryPool&& memoryPool) noexcept;

            pointer address(reference x) const noexcept;
            const_pointer address(const_reference x) const noexcept;

            // Can only allocate one object at a time. n and hint are ignored
            pointer allocate(size_type n = 1, const_pointer hint = 0);
            void deallocate(pointer p, size_type n = 1);

            size_type max_size() const noexcept;

            template <class U, class... Args> void construct(U* p, Args&&... args);
            template <class U> void destroy(U* p);

            template <class... Args> pointer newElement(Args&&... args);
            void deleteElement(pointer p);

        private:
            struct Element_ {
                Element_* pre;
                Element_* next;
            };

            typedef char* data_pointer;
            typedef Element_ element_type;
            typedef Element_* element_pointer;

            element_pointer data_element_;
            element_pointer free_element_;

            std::recursive_mutex m_;

            size_type padPointer(data_pointer p, size_type align) const noexcept;
            void allocateBlock();

            static_assert(BlockSize >= 2 * sizeof(element_type), "BlockSize too small.");
        };


        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        inline typename MemoryPool<T, BlockSize, ZeroOnDeallocate>::size_type
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::padPointer(data_pointer p, size_type align)
            const noexcept {
            uintptr_t result = reinterpret_cast<uintptr_t>(p);
            return ((align - result) % align);
        }



        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        MemoryPool<T, BlockSize, ZeroOnDeallocate>::MemoryPool()
            noexcept {
            data_element_ = nullptr;
            free_element_ = nullptr;
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        MemoryPool<T, BlockSize, ZeroOnDeallocate>::MemoryPool(const MemoryPool& memoryPool)
            noexcept :
            MemoryPool() {
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        MemoryPool<T, BlockSize, ZeroOnDeallocate>::MemoryPool(MemoryPool&& memoryPool)
            noexcept {
            std::lock_guard<std::recursive_mutex> lock(m_);

            data_element_ = memoryPool.data_element_;
            memoryPool.data_element_ = nullptr;
            free_element_ = memoryPool.free_element_;
            memoryPool.free_element_ = nullptr;
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        template<class U>
        MemoryPool<T, BlockSize, ZeroOnDeallocate>::MemoryPool(const MemoryPool<U>& memoryPool)
            noexcept :
            MemoryPool() {
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        MemoryPool<T, BlockSize, ZeroOnDeallocate>&
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::operator=(MemoryPool&& memoryPool)
            noexcept {
            std::lock_guard<std::recursive_mutex> lock(m_);

            if (this != &memoryPool) {
                std::swap(data_element_, memoryPool.data_element_);
                std::swap(free_element_, memoryPool.free_element_);
            }
            return *this;
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        MemoryPool<T, BlockSize, ZeroOnDeallocate>::~MemoryPool()
            noexcept {
            std::lock_guard<std::recursive_mutex> lock(m_);

            element_pointer curr = data_element_;
            while (curr != nullptr) {
                element_pointer prev = curr->next;
                operator delete(reinterpret_cast<void*>(curr));
                curr = prev;
            }

            curr = free_element_;
            while (curr != nullptr) {
                element_pointer prev = curr->next;
                operator delete(reinterpret_cast<void*>(curr));
                curr = prev;
            }
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        inline typename MemoryPool<T, BlockSize, ZeroOnDeallocate>::pointer
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::address(reference x)
            const noexcept {
            return &x;
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        inline typename MemoryPool<T, BlockSize, ZeroOnDeallocate>::const_pointer
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::address(const_reference x)
            const noexcept {
            return &x;
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        void
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::allocateBlock() {
            // Allocate space for the new block and store a pointer to the previous one
            data_pointer new_block = reinterpret_cast<data_pointer> (operator new(BlockSize));
            element_pointer new_ele_pointer = reinterpret_cast<element_pointer>(new_block);
            new_ele_pointer->pre = nullptr;
            new_ele_pointer->next = nullptr;

            if (data_element_) {
                data_element_->pre = new_ele_pointer;
            }

            new_ele_pointer->next = data_element_;
            data_element_ = new_ele_pointer;
        }

        template <typename T, size_t BlockSize,  bool ZeroOnDeallocate>
        inline typename MemoryPool<T, BlockSize, ZeroOnDeallocate>::pointer
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::allocate(size_type n, const_pointer hint) {
            std::lock_guard<std::recursive_mutex> lock(m_);

            if (free_element_ != nullptr) {
                data_pointer body =
                    reinterpret_cast<data_pointer>(reinterpret_cast<data_pointer>(free_element_) + sizeof(element_type));

                size_type bodyPadding = padPointer(body, alignof(element_type));

                pointer result = reinterpret_cast<pointer>(reinterpret_cast<data_pointer>(body + bodyPadding));

                element_pointer tmp = free_element_;

                free_element_ = free_element_->next;

                if (free_element_)
                    free_element_->pre = nullptr;

                tmp->next = data_element_;
                if (data_element_)
                    data_element_->pre = tmp;
                tmp->pre = nullptr;
                data_element_ = tmp;

                return result;
            }
            else {
                allocateBlock();

                data_pointer body =
                    reinterpret_cast<data_pointer>(reinterpret_cast<data_pointer>(data_element_) + sizeof(element_type));

                size_type bodyPadding = padPointer(body, alignof(element_type));

                pointer result = reinterpret_cast<pointer>(reinterpret_cast<data_pointer>(body + bodyPadding));

                return result;
            }
        }

        template <typename T, size_t BlockSize, bool ZeroOnDeallocate>
        inline void
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::deallocate(pointer p, size_type n) {
            std::lock_guard<std::recursive_mutex> lock(m_);

            if (p != nullptr) {
                element_pointer ele_p =
                    reinterpret_cast<element_pointer>(reinterpret_cast<data_pointer>(p) - sizeof(element_type));

                if (ZeroOnDeallocate) {
                    memset(reinterpret_cast<data_pointer>(p), 0, BlockSize - sizeof(element_type));
                }

                if (ele_p->pre) {
                    ele_p->pre->next = ele_p->next;
                }

                if (ele_p->next) {
                    ele_p->next->pre = ele_p->pre;
                }

                if (ele_p->pre == nullptr) {
                    data_element_ = ele_p->next;
                }

                ele_p->pre = nullptr;
                if (free_element_) {
                    ele_p->next = free_element_;
                    free_element_->pre = ele_p;
                }
                else {
                    ele_p->next = nullptr;
                }
                free_element_ = ele_p;
            }
        }

        template <typename T, size_t BlockSize, bool ZeroOnDeallocate>
        inline typename MemoryPool<T, BlockSize, ZeroOnDeallocate>::size_type
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::max_size()
            const noexcept {
            size_type maxBlocks = -1 / BlockSize;
            return (BlockSize - sizeof(data_pointer)) / sizeof(element_type) * maxBlocks;
        }

        template <typename T, size_t BlockSize, bool ZeroOnDeallocate>
        template <class U, class... Args>
        inline void
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::construct(U* p, Args&&... args) {
            new (p) U(std::forward<Args>(args)...);
        }

        template <typename T, size_t BlockSize, bool ZeroOnDeallocate>
        template <class U>
        inline void
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::destroy(U* p) {
            p->~U();
        }

        template <typename T, size_t BlockSize, bool ZeroOnDeallocate>
        template <class... Args>
        inline typename MemoryPool<T, BlockSize, ZeroOnDeallocate>::pointer
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::newElement(Args&&... args) {
            std::lock_guard<std::recursive_mutex> lock(m_);
            pointer result = allocate();
            construct<value_type>(result, std::forward<Args>(args)...);
            return result;
        }

        template <typename T, size_t BlockSize, bool ZeroOnDeallocate>
        inline void
            MemoryPool<T, BlockSize, ZeroOnDeallocate>::deleteElement(pointer p) {
            std::lock_guard<std::recursive_mutex> lock(m_);
            if (p != nullptr) {
                p->~value_type();
                deallocate(p);
            }
        }
    }
}

#endif // PPX_BASE_MEMORY_POOL_H_

使用示例:


#include <IOStream>
#include <thread>
using namespace std;



class Apple {
public:
    Apple() {
        id_ = 0;
        cout << "Apple()" << endl;
    }

    Apple(int id) {
        id_ = id;
        cout << "Apple(" << id_ << ")" << endl;
    }

    ~Apple() {
        cout << "~Apple()" << endl;
    }

    void SetId(int id) {
        id_ = id;
    }

    int GetId() {
        return id_;
    }
private:
    int id_;
};



void ThreadProc(ppx::base::MemoryPool<char> *mp) {
    int i = 0;
    while (i++ < 100000) {
        char* p0 = (char*)mp->allocate();

        char* p1 = (char*)mp->allocate();

        mp->deallocate(p0);

        char* p2 = (char*)mp->allocate();

        mp->deallocate(p1);
        
        mp->deallocate(p2);

    }
}

int main()
{
    ppx::base::MemoryPool<char> mp;
    int i = 0;
    while (i++ < 100000) {
        char* p0 = (char*)mp.allocate();

        char* p1 = (char*)mp.allocate();

        mp.deallocate(p0);

        char* p2 = (char*)mp.allocate();

        mp.deallocate(p1);

        mp.deallocate(p2);

    }

    std::thread th0(ThreadProc, &mp);
    std::thread th1(ThreadProc, &mp);
    std::thread th2(ThreadProc, &mp);

    th0.join();
    th1.join();
    th2.join();

    Apple *apple = nullptr;
    {
        ppx::base::MemoryPool<Apple> mp2;
        apple = mp2.newElement(10);
        int a = apple->GetId();
        apple->SetId(10);
        a = apple->GetId();

        mp2.deleteElement(apple);
    }

    apple->SetId(12);
    int b = -4 % 4;

    int *a = nullptr;
    {
        ppx::base::MemoryPool<int, 18> mp3;
        a =  mp3.allocate();
        *a = 100;
        //mp3.deallocate(a);

        int *b =  mp3.allocate();
        *b = 200;
        //mp3.deallocate(b);

        mp3.deallocate(a);
        mp3.deallocate(b);

        int *c = mp3.allocate();
        *c = 300;
    }

    getchar();
    return 0;
}


Tags:C++   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一、编程语言1.根据熟悉的语言,谈谈两种语言的区别?主要浅谈下C/C++和PHP语言的区别:1)PHP弱类型语言,一种脚本语言,对数据的类型不要求过多,较多的应用于Web应用开发,现在好多互...【详细内容】
2021-12-15  Tags: C++  点击:(17)  评论:(0)  加入收藏
函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。在参数传递中,有两个很...【详细内容】
2021-11-30  Tags: C++  点击:(19)  评论:(0)  加入收藏
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  Tags: C++  点击:(37)  评论:(0)  加入收藏
C++编程中,你是否有为 我到底该写个struct还是class 而苦恼过?如果你到现在还不知道该如何选择,那么请求继续阅读,下文或许能给你些建议。问题的产生C++语言继承了 C语言的 stru...【详细内容】
2021-10-18  Tags: C++  点击:(61)  评论:(0)  加入收藏
C++在C的面向过程概念的基础上提供了面向对象和模板(泛型编程)的语法功能。下面以一个简单实例(动态数组的简单封装,包括下标的值可以是任意正数值,并提供边界检查)来说明C++是如...【详细内容】
2021-10-18  Tags: C++  点击:(49)  评论:(0)  加入收藏
0 前言Hello,大家好,欢迎来到『自由技艺』的 C++ 系列专题。代码重用,尽可能避免冗余代码是程序员的一项必备技能,今天就来给大家介绍其中一种:函数装饰器。在设计模式中,与它对应...【详细内容】
2021-09-28  Tags: C++  点击:(75)  评论:(0)  加入收藏
今天我们就来聊一聊 C++ 中的异常机制吧。在学校期间,我们很少会用得上异常机制。然而,工作之后,很多时候却不得不引入异常机制。因为一般情况下,使用函数的返回值来确定函数的...【详细内容】
2021-09-26  Tags: C++  点击:(181)  评论:(0)  加入收藏
一、内存泄漏(memory leak)内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存...【详细内容】
2021-09-03  Tags: C++  点击:(104)  评论:(0)  加入收藏
stack容器#include <iostream>using namespace std;#include <stack>//容器头文件void test(){stack<int>p;p.push(100);p.push(1000);p.push(100);while(!p.empty()){cout<...【详细内容】
2021-08-17  Tags: C++  点击:(81)  评论:(0)  加入收藏
stl 常用遍历算法(for_each transform)示例代码(结论在结尾!!!!)#include<iostream>using namespace std;#include"vector"#include"map"#include"string"#include"list"#in...【详细内容】
2021-08-13  Tags: C++  点击:(89)  评论:(0)  加入收藏
▌简易百科推荐
一、简介很多时候我们都需要用到一些验证的方法,有时候需要用正则表达式校验数据时,往往需要到网上找很久,结果找到的还不是很符合自己想要的。所以我把自己整理的校验帮助类分...【详细内容】
2021-12-27  中年农码工    Tags:C#   点击:(0)  评论:(0)  加入收藏
引言在学习C语言或者其他编程语言的时候,我们编写的一个程序代码,基本都是在屏幕上打印出 hello world ,开始步入编程世(深)界(坑)的。C 语言版本的 hello world 代码:#include <std...【详细内容】
2021-12-21  一起学嵌入式    Tags:C 语言   点击:(10)  评论:(0)  加入收藏
读取SQLite数据库,就是读取一个路径\\192.168.100.**\position\db.sqlite下的文件<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0"/...【详细内容】
2021-12-16  今朝我的奋斗    Tags:c#   点击:(21)  评论:(0)  加入收藏
什么是shellshell是c语言编写的程序,它在用户和操作系统之间架起了一座桥梁,用户可以通过这个桥梁访问操作系统内核服务。 它既是一种命令语言,同时也是一种程序设计语言,你可以...【详细内容】
2021-12-16  梦回故里归来    Tags:shell脚本   点击:(16)  评论:(0)  加入收藏
一、编程语言1.根据熟悉的语言,谈谈两种语言的区别?主要浅谈下C/C++和PHP语言的区别:1)PHP弱类型语言,一种脚本语言,对数据的类型不要求过多,较多的应用于Web应用开发,现在好多互...【详细内容】
2021-12-15  linux上的码农    Tags:c/c++   点击:(17)  评论:(0)  加入收藏
1.字符串数组+初始化char s1[]="array"; //字符数组char s2[6]="array"; //数组长度=字符串长度+1,因为字符串末尾会自动添&lsquo;\0&lsquo;printf("%s,%c\n",s1,s2[2]);...【详细内容】
2021-12-08  灯-灯灯    Tags:C语言   点击:(46)  评论:(0)  加入收藏
函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。在参数传递中,有两个很...【详细内容】
2021-11-30  小智雅汇    Tags:函数   点击:(19)  评论:(0)  加入收藏
一、问题提出问题:把m个苹果放入n个盘子中,允许有的盘子为空,共有多少种方法?注:5,1,1和1 5 1属同一种方法m,n均小于10二、算法分析设f(m,n) 为m个苹果,n个盘子的放法数目,则先对...【详细内容】
2021-11-17  C语言编程    Tags:C语言   点击:(46)  评论:(0)  加入收藏
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  深度Linux    Tags:C++   点击:(37)  评论:(0)  加入收藏
OpenCV(Open Source Computer Vision Library)是一个(开源免费)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android、ios等操作系统上,它轻量级而且高效---由一系列...【详细内容】
2021-11-11  zls315    Tags:C#   点击:(50)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条