用jemalloc代替glibc默认ptmalloc进一步提升服务器性能和负载

     启动redis时,无意中看到redis的启动信息有一个jemalloc的版本信息,处于好奇了解了一下,它是一个进一步提升服务器负载和性能的神器。

一  Ptmalloc

      Linux 系统在装载 elf 格式的程序文件时,会调用 loader 把可执行文件中的各个段依次载入到从某一地址开始的空间中(载入地址取决 link editor(ld)和机器地址位数,在 32 位机器上是 0x8048000,即 128M 处)。如下图所示,以 32 位机器为例,首先被载入的是.text 段,然后是.data 段,最后是.bss 段。这可以看作是程序的开始空间。程序所能访问的最后的地址是 0xbfffffff,也就是到 3G 地址处,3G 以上的 1G 空间是内核使用的,应用程序不可以直接访问。应用程序的堆栈从最高地址处开始向下生长,.bss 段与堆栈之间的空间是空闲的,空闲空间被分成两部分,一部分为 heap,一部分为 mmap 映射区域,mmap 映射区域一般从 TASK_SIZE/3 的地方开始,但在不同的 Linux 内核和机器上,mmap 区域的开始位置一般是不同的。Heap 和 mmap 区域都可以供用户自由使用,但是它在刚开始的时候并没有映射到内存空间内,是不可访问的。在向内核请求分配该空间之前,对这个空间的访问会导致segmentation fault。用户程序可以直接使用系统调用来管理 heap 和 mmap 映射区域,但更多的时候程序都是使用 C 语言提供的 malloc()和 free()函数来动态的分配和释放内存。Stack区域是唯一不需要映射,用户却可以访问的内存区域,这也是利用堆栈溢出进行攻击的基础。

      Linux 中 malloc 的早期版本是由 Doug Lea 实现的,它有一个重要问题就是在并行处理时多个线程共享进程的内存空间,各线程可能并发请求内存,在这种情况下应该如何保证分配和回收的正确和高效。Wolfram Gloger 在 Doug Lea 的基础上改进使得 Glibc 的 malloc 可以支持多线程——ptmalloc,在glibc-2.3.x.中已经集成了ptmalloc2,这就是我们平时使用的malloc,目前 ptmalloc 的最新版本 ptmalloc3。ptmalloc2 的性能略微比 ptmalloc3 要高一点点。ptmalloc 实现了 malloc(),free()以及一组其它的函数. 以提供动态内存管理的支持。分配器处在用户程序和内核之间,它响应用户的分配请求,向操作系统申请内存,然后将其返回给用户程序,为了保持高效的分配,分配器一般都会预先分配一块大于用户请求的内存,并通过某种算法管理这块内存。来满足用户的内存分配要求,用户释放掉的内存也并不是立即就返回给操作系统,相反,分配器会管理这些被释放掉的空闲空间,以应对用户以后的内存分配要求。也就是说,分配器不但要管理已分配的内存块,还需要管理空闲的内存块,当响应用户分配要求时,分配器会首先在空闲空间中寻找一块合适的内存给用户,在空闲空间中找不到的情况下才分配一块新的内存。为实现一个高效的分配器,需要考虑很多的因素。比如,分配器本身管理内存块所占用的内存空间必须很小,分配算法必须要足够的快。

   Ptmalloc 在设计时折中了高效率,高空间利用率,高可用性等设计目标。在其实现代码中,隐藏着内存管理中的一些设计假设,由于某些设计假设,导致了在某些情况下 ptmalloc的行为很诡异。这些设计假设包括:
1. 具有长生命周期的大内存分配使用 mmap。
2. 特别大的内存分配总是使用 mmap。
3. 具有短生命周期的内存分配使用 brk,因为用 mmap 映射匿名页,当发生缺页异常时,linux 内核为缺页分配一个新物理页,并将该物理页清 0,一个 mmap 的内存块需要映射多个物理页,导致多次清 0 操作,很浪费系统资源,所以引入了 mmap
分配阈值动态调整机制,保证在必要的情况下才使用 mmap 分配内存。
4. 尽量只缓存临时使用的空闲小内存块,对大内存块或是长生命周期的大内存块在释放时都直接归还给操作系统。
5. 对空闲的小内存块只会在 malloc 和 free 的时候进行合并,free 时空闲内存块可能放入 pool 中,不一定归还给操作系统。
6. 收缩堆的条件是当前 free 的块大小加上前后能合并 chunk 的大小大于 64KB、,并且堆顶的大小达到阈值,才有可能收缩堆,把堆最顶端的空闲内存返回给操作系统。
7. 需要保持长期存储的程序不适合用 ptmalloc 来管理内存。
8. 为了支持多线程,多个线程可以从同一个分配区(arena)中分配内存,ptmalloc假设线程 A 释放掉一块内存后,线程 B 会申请类似大小的内存,但是 A 释放的内存跟 B 需要的内存不一定完全相等,可能有一个小的误差,就需要不停地对内存块作切割和合并,这个过程中可能产生内存碎片。

     如果对想深入了解ptmalloc的内存管理方式,看大神的研究成果吧,比较晦涩难懂:http://www.valleytalk.org/wp-content/uploads/2015/02/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%901.pdf

二  jemalloc

       jemalloc是一个能够快速分配/回收内存,减少内存碎片,对多核友好,具有可伸缩性的内存分配器。为更好的支持多核内存分配的需要,jemalloc首次在FreeBSD中引,而后被facebook发扬光大,成为了业界流行的内存分配解决方案之一。

      jemalloc的内存管理采用层级架构, 分别是线程缓存tcache, 分配区arena和系统内存memory, 不同大小的内存块对应不同的分配区。每个线程对应一个tcache, 负责当前线程使用内存块的快速申请和释放, 避免线程间锁的竞争和同步。分配arena的具体结构在前文已经提到, 采用内存池的思想对内存区域进行合理的划分和管理, 在有效保证低内存碎片的情况下实现不同大小内存块的高效管理。 system memory是系统的内存区域。

     small object:当jemalloc支持tcache时, small object的分配从tcache开始, tcache不中则从arena申请run并将剩余区域缓存到tcache, 若从arena中不能分配再从system memory中申请chunk加入arena进行管理, 不支持tcache时, 则直接从arena中申请。
     arge object: 当jemalloc支持tcache时, 如果large object的size小于tcache_maxclass,则从tcache开始分配, tcache不中则从arena申请, 只申请需要的内存块, 不做多余cache, 若从arena中不能分配则从system memory中申请。当large object的size大于tcache_maxclass或者jemmalloc不支持tcache时, 直接从arena中申请。
     huge object: huge object的内存不归arena管理, 直接采用mmap从system memory中申请并由一棵与arena独立的红黑树进行管理。
      jemalloc引入线程缓存tcache, 分配区arena来减少线程间锁的争用, 保证线程并发扩展性的同时实现了内存的快速申请释放
采用arena管理不同大小的内存对象在保证内存高效管理的同时减少了内存碎片引入红黑树管理空闲run和chunk, 相比链表具有了更高的效率在run中采用bitmap管理可分配区域来实现管理和数据分离, 且能够更快地定位到空闲区域引入了多层cache, 基于内存池的思想, 虽然增加了内存占用, 但实现了内存的快速申请释放, 除了tcache,还有bin中的runcur, arena中的spare等。

      以上知识一些简单的介绍,当然也都是搜集来的一些简要的概括,更详细的列出一些好的文章(说实话这些东西看起来确确实优点费脑子):

   1  内存优化总结:ptmalloc、tcmalloc和jemalloc:http://www.cnhalo.net/2016/06/13/memory-optimize/,里面有一些详细的性能数据对比,可以看一下。

   2 jemalloc 的源码分析:http://www.thinkingyu.com/articles/jemalloc/

   3 几种malloc实现原理 ptmalloc  tcmalloc jemallochttps://blog.csdn.net/huangynn/article/details/50700093

三  使用jemalloc 

     首先 git clone https://github.com/jemalloc/jemalloc.git
     为了验证是否替换成功,我们现在源码的关键函数处加一些log

    jemalloc_cpp.cpp  

  jemalloc.c

进入源码木目录: ./autogen.sh && make && sudo make install 

安装过程中会提示autoconf版本过低,先升级下autoconf,还会提示doc目录下缺少几个文件,直接touch + 缺少文件名,创建空文件即可

class CJeMallocTest {
    int a;
    char b;
};

int main ()
{
  CJeMallocTest* cMallocTest  = (CJeMallocTest*)malloc (sizeof (CJeMallocTest));
  free (cMallocTest);
  CJeMallocTest* cppJeMallocTest  = new CJeMallocTest;
  delete cppJeMallocTest;
  cppJeMallocTest = nullptr;
}

    编译的时候-ljemalloc 即可

     

运行结果

  最后把添加的log去掉重新编译编译安装。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值