内存池是什么原理?|内存池简易模拟实现|为学习高并发内存池tcmalloc做准备

前言

那么这里博主先安利一些干货满满的专栏了!

这两个都是博主在学习Linux操作系统过程中的记录,希望对大家的学习有帮助!

操作系统Operating Syshttps://blog.csdn.net/yu_cblog/category_12165502.html?spm=1001.2014.3001.5482Linux Syshttps://blog.csdn.net/yu_cblog/category_11786077.html?spm=1001.2014.3001.5482这两个是博主学习数据结构的同时,手撕模拟STL标准模版库各种容器的专栏。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html


什么是内存池

因此最近博主准备开始学习Google的tcmalloc技术了,其实它就是一个高并发的内存池,效率做到极致,因此在此之前,博主先稍微学习了一下内存池的基本技术和基本概念,为后面的高并发内存池项目做准备,今天先给大家带来这个简单的内存池实现技术的简单demo代码。

所以本博客的代码,这是内存池的demo代码,实际上的内存池,比这个要复杂的多!这里的代码只是供学习使用。tcmalloc技术才是学习内存池的目标。

什么是内存池?

内存池是一种用来管理计算机程序中内存分配和释放的技术。它类似于一个预先准备好的内存存储区域,程序可以从中获取内存块而不是频繁地向操作系统请求。这种方式更高效,因为内存块的分配和释放开销较小,并且可以避免碎片化。内存池提高了程序的性能和响应速度,特别适用于频繁创建和销毁对象的场景,如网络服务器和并发编程。C语言的malloc,本质就是一个内存池。

内存池的基本原理

为了更高效的处理向内存获取资源,向内存获取资源,向内存释放资源,内存池一般这么做:

博主为了大家好理解,说的比较通俗:

  • 内存池初始化的时候,直接向内存先要一大块资源。
  • 向内存池获取资源的时候,从刚才初始化获取的一大块内存中切出一部分交给上层使用。
  • 当进程向内存池释放资源的时候,不直接释放到系统中,而是首先把不用的空间,交给内存池,内存池内部维护一个单链表,把还回来的资源串起来。
  • 当大块内存用完的时候,再向系统索要新内存。
  • 当进程索取资源的时候,优先从链表中获取还回来的资源,如果链表中没有资源了,再向系统索要资源。

ObjectPool.hpp


#ifndef __OBJECT_POOL__
#define __OBJECT_POOL__

#include <iostream>
#include <vector>
// #include "../utils/Log.hpp"  // 大家下载代码的时候可以暂时不要日志

#define __DEFAULT_KB__ 128

/*
    其实这里直接使用malloc的,malloc其实自己就是C语言的一个内存池,
    其实我们可以直接用系统调用,跳过malloc,这样测试现象更明显
    调用SystemAlloc替换malloc即可
*/

inline static void *SystemAlloc(size_t kpage)
{
    void *ptr = nullptr;
#ifdef _WIN32
    *ptr = VirtualAlloc(0, kpage * (1 << 12), MEM_COMMIT | MEM_RESERVE,
                             PAGE_READWRITE);
#else
    // linux下brk mmap等 #endif
    
    if (ptr == nullptr)
        throw std::bad_alloc();
    return ptr;
#endif
}

template <class T>
class ObjectPool
{
private:
    char *__memory = nullptr;    // char 方便切
    size_t __remain_bytes = 0;   // 大块内存在切的过程中剩余的字节数
    void *__free_list = nullptr; // 还回来的时候形成的自由链表
public:
    T *New()
    {
        T *obj = nullptr;
        // 不够空间 首选是把还回来的内存块对象进行再次利用
        if (__free_list)
        {
            // 头删
            void *next = *((void **)__free_list);
            obj = (T *)__free_list;
            __free_list = next;
            return obj;
        }
        if (__remain_bytes < sizeof(T))
        {
            // 空间不够了,要重新开一个空间
            __remain_bytes = __DEFAULT_KB__ * 1024;
            __memory = (char *)malloc(__remain_bytes);
            if (__memory == nullptr)
            {
                throw std::bad_alloc();
                // logMessage(ERROR, "ObjectPool::New() malloc error");
            }
        }
        obj = (T *)__memory;
        size_t obj_size = sizeof(T) < sizeof(void *) ? sizeof(void *) : sizeof(T); // 小于一个指针的大小就给一个指针的大小就行
        __memory += obj_size;
        __remain_bytes -= obj_size;
        // 定位new,显示调用T的构造函数,让T初始化一下
        new (obj) T;
        return obj;
    }
    void Delete(T *obj)
    {
        // 这样写无论是第一次插入还是后面的插入,都可以
        // 这样写无论是32位还是64位,都可以
        obj->~T(); // 显示调用析构函数
        *(void **)obj = __free_list;
        __free_list = obj;
    }
};

#endif

testPerf.hpp

用于测试这个简易内存池的性能


#include "ObjectPool.hpp"

struct TreeNode
{
    int _val;
    TreeNode *_left;
    TreeNode *_right;
    TreeNode()
        : _val(0), _left(nullptr), _right(nullptr)
    {
    }
};
void TestObjectPool()
{
    // 申请释放的轮次
    const size_t Rounds = 5;
    // 每轮申请释放多少次
    const size_t N = 10000000;
    size_t begin1 = clock();
    std::vector<TreeNode *> v1;
    v1.reserve(N);
    for (size_t j = 0; j < Rounds; ++j)
    {
        for (int i = 0; i < N; ++i)
        {
            v1.push_back(new TreeNode);
        }
        for (int i = 0; i < N; ++i)
        {
            delete v1[i];
        }
        v1.clear();
    }
    size_t end1 = clock();
    ObjectPool<TreeNode> TNPool;
    size_t begin2 = clock();
    std::vector<TreeNode *> v2;
    v2.reserve(N);
    for (size_t j = 0; j < Rounds; ++j)
    {
        for (int i = 0; i < N; ++i)
        {
            v2.push_back(TNPool.New());
        }
        for (int i = 0; i < N; ++i)
        {
            TNPool.Delete(v2[i]);
        }
        v2.clear();
    }
    size_t end2 = clock();
    std::cout << "new cost time:" << end1 - begin1 << std::endl;
    std::cout << "object pool cost time:" << end2 - begin2 << std::endl;
}

test.cc

#include "testPerf.hpp"
int main()
{
    TestObjectPool();
    return 0;
}

测试结果

posted @ 2023-07-15 17:24  背包Yu  阅读(13)  评论(0编辑  收藏  举报  来源