zoukankan      html  css  js  c++  java
  • (转)内存池实现

    话说一直想找一个别人写好的使用,可惜没什么人会拿这小东西发布,只好自写一个。

    1.多级链表分配池
    我不知道这种设计的具体学名是什么,这部分的内容也许你去看《STL源码分析》的有关章节更合适一些,这里我只能用我粗陋的语言描述一下。
    内存池,完全可以从字面上理解为从池子里申请内存,释放的时候还给池子。
    最简单的内存池应该是fix_pool吧,即每次分配出来的内存块大小是固定的。这种池子的管理结构是一个链表,链表的每一个节点为固定大小的内存块。分配的时候,直接返回链表的第一个节点,节点不足时,从系统申请大块内存分成多个节点加入链表;释放的时候更简单,将释放的内存加入链表头。
    假设fix_pool的fix size = 128,那么内存池可以为128byte以下的任意大小的请求进行分配,但是这样做相当浪费呢,于是unfix_pool就在此基础上出现了。
    由多个分配大小不同的fix_pool所组成的内存池就叫做多级链表分配池,我是这么定义的。
    常规上会定义8,16,24,32,...,112,120,128这些分配大小,共16级。分配或者释放的时候,判断请求的大小在哪一级别上,用该级别的fix_pool链表进行分配或者释放。


    2.泄漏检测
    当所有的分配都经过你的手的时候,泄漏检测什么的再简单不过了。
    找个地方把分配的东西记录下来,释放的时候把记录去掉。程序退出的时候还存在的分配记录就是泄漏了。
    我个人选用的方法是给每一个分配请求多分配一些内存,用来记录分配的信息,并将这部分信息用双向链表串起来。释放的时候对释放的指针做一下指针偏移就可以找到信息记录并移出双向链表。
    这个方法的开销是常数级的,不过无法处理重复删除的问题。


    3.operater new
    要把你的内存池应用到每一个角落,需要定义operator new和operator delete。
     void* operator new(size_t) throw(std::bad_alloc);
     void operator delete(void* p);
    但是这还不够,谁也不想看到一堆泄漏信息而找不到泄漏的位置,因此还需要定义带附加参数的operator。
    对于placement new而言,operator new[]和operator delete[]是必须的,无法省略。

    void* operator new(size_t, const char* file, int line, const char* function);
    void* operator new[](size_t, const char*intconst char*);
    void operator delete(void* p);
    void operator delete[](void* p);
    为了能用上新的operator,需要在头文件中重新定义new,并包含进每一个cpp文件。

    //op_new.h
    #define DEBUG_NEW new(__FILE__, __LINE__, __FUNCTION__)
    #define new DEBUG_new
    不过重定义new会和自行使用placement new的地方冲突,如stl容器库,这时候要undef new后才能编译冲突组件。

    #undef new
    #include 
    <vector>
    #include 
    "op_new.h"


    4.线程安全
    我没听说过new/delete,malloc/free是线程不安全的,所以在内存池的allocate/deallocate接口处直接加了锁。
    想降低开销的同学可以使用spin lock,而不是mutex。


    5.bench
    AMD5000+ X2, memory 2G,测试分配大概900M
     1     for(int x=0; x<REPEAT; ++x)
     2     {
     3         clock_t t1 = clock();
     4         for(int i=0; i<15990000++i)
     5         {
     6             size_t size = rand() % 121;
     7             char* p = new char[size];
     8             bufs.push_back(p);
     9         }
    10         tm = tm + clock() - t1;
    11         printf("time alloc %d\n", tm);
    12 
    13         t1 = clock();
    14         for(int i=0; i<bufs.size(); ++i)
    15         {
    16             char* p = bufs[i];
    17             delete [] p;
    18         }
    19         t2 = t2 + clock() - t1;
    20         printf("time free %d\n", t2);
    21         bufs.clear();
    22     }

    repeat=1
    win32下分配效率提升大概50%,释放效率提升170%;
    linux下技不如人,输了。。。。

    repeat=15,应该存在内存碎片这种东西了
    win32下分配效率提升100%,释放效率提升140%;
    linux下分配效率提升大概15%左右,释放效率提升50%以上。

    猜测结论: linux的内存分配机制很高效。我的实现可能写得不怎样,或者内存池已经out了。

    补充:
    由eXile推荐的tcmalloc,进行了性能测试,linux平台
    repeat=1
    tcmalloc和系统分配半斤八两,难出其右。
    repeat=15
    tcmalloc分配效率提升30%以上,释放效率提升100%以上。
    我想果然还是我实现里使用mutex的缘故,去掉加锁后,速度超英赶美,释放效率更是比tcmalloc提升了50%以上。
    也许将mutex换成spin lock就能和tcmalloc的效率接近了,但是对于thread cache这一点我是没法比的,可以不加锁分配,多线程下的效率很高。
    不过既然有tcmalloc,自己写这种general pool就没有什么必要了。
  • 相关阅读:
    webstorm配置less解析的方法
    C#面试题(String和StringBuilder区别)
    Winform控件输入的字母转换成大写
    C#之实参和形参
    我学React Native开发的经历(一) 第一周学习,环境搭建及demo1,一个导航跳转页面
    三、CSS样式——背景
    二、CSS选择器
    一、CSS介绍
    九、非主体机构元素——header元素、footer元素、address元素、网页编排
    八(第三篇)、主体结构元素——time元素、pubdate属性
  • 原文地址:https://www.cnblogs.com/lancidie/p/1833968.html
Copyright © 2011-2022 走看看