内存分配_内存分配策略及组件比较

       一般而言,应用程序想要提高性能,常常使用的策略就是池化技术。常见的池化技术有线程池、数据库连接池,还有内存分配的内存池等。这些池化的方案主要是为了减少反复创建,达到重复利用现有资源的目的。正好最近看完了Linux内核中内存管理伙伴算法,本文将比较介绍Linux中的几种内存分配策略。     通常来说,目前有三种内存分配管理组件,分别是ptmalloc/tcmalloc/jemalloc。其中,ptmalloc是glibc默认的分配策略,tcmalloc是google开源的一个内存分配器,jemalloc是facebook开源的一个内存分配器。其中,Linux下开发默认的glibc库是ptmalloc分配器。tcmalloc/jemalloc只需要使用LD_PRELOAD环境变量启动程序即可,甚至并不需要重新编译。

备注:ptmalloc在2006年出来了ptmalloc3,其作者是Java高并发系列的作者Doug Lea,本文介绍的ptmalloc为ptmalloc2。

      最近看到有些人和文章谈到,目前大部分服务端程序使用glibc,而其中ptmalloc2在性能上远远弱后于google的tcmalloc和facebook的jemalloc。于是看了一下官方手册并做一下实验对比一下看看tcmalloc/ptmalloc2性能。        那么怎么使用tcmalloc或者jemalloc呢?这里以tcmalloc的安装使用为例:
  • 使用TCMalloc库前,需要下载编译它。

  • 预安装autoconf、automake、libtool 工具。我本地是Ubuntu16系统,使用apt-get install autoconf、automake、libtool分别安装。

  • 安装gperftool,下载地址为

    https://github.com/gperftools/gperftools.git

  • 下载完然后make并make install 安装即可。

      测试代码比较简单,主体代码如下:
void* loopalloc(void* args) {      timeval tStart,tEnd;      void* addr;      gettimeofday(&tStart, 0);     for (int i = 0; i < TESTN; i++) {         addr = malloc(size);         free(addr);     }     gettimeofday(&tEnd, 0);     //将消耗时间传出到timecost数组中对应的元素上    *(long*)args=(1000000LL*(tEnd.tv_sec-tStart.tv_sec) + (tEnd.tv_usec-tStart.tv_usec));}   int main(int argc, char** argv) {        int threadnum = 1;        int ch;        long* timecost = new long[threadnum];        //解析参数,统计每个线程计算所需要的时间      pthread_create(&id[i],NULL,loopalloc,&timecost[i]);      //等待所有线程结束      for(int i = 0; i < threadnum; i++) {         pthread_join(id[i],NULL);         costsum += timecost[i];     }     //比较平均每线程所用时间     printf("%d线程 %d字节:%.2f纳秒\n", threadnum,size,(double)costsum/threadnum/TESTN*1000);}
           代码通过main函数的参数传入分配的内存大小、线程数量以及运行次数,通过计算起始和结束的时间差,然后通过loopalloc参数传回给main函数用于输出结果。

单线程测试结果:

  • 分配小于256K 字节的内存,ptmalloc2 耗时 32 纳秒,而 tcmalloc仅耗时 10 纳秒。说明tcmalloc在小内存分配方面优化了。
  • 单线程下分配 257K 字节的内存,ptmalloc2 的耗时仍然是 32 纳秒,但 tcmalloc 就由 10 纳秒上升到 64 纳秒。tcmalloc 反过来比ptmalloc2 慢了 1 倍!这是因为 tcmalloc特意针对小内存做了优化。
  • 当测试更大的内存,使用2M内存的分配。ptmalloc2耗时依然在 32 纳秒,但 tcmalloc 耗时已经到了 80 纳秒。

   多线程测试:

  • 在10个线程下,分配释放256KB大小内存,ptmalloc2耗时34纳秒,而tcmalloc耗时11纳秒。这其实是因为ptmalloc2假设线程A申请的内存,当释放的时候保留了该内存防止其他线程也要用,也就是允许复用内存提升性能。因此ptmalloc2每次申请释放内存都会加独占锁,tcmalloc使用的是自旋锁。

  • 当线程数量放大到40个线程,ptmalloc2和tcmalloc的耗时更加大,也就是说多线程环境下,tcmalloc比ptmalloc2更有优势。下面是tcmalloc的官方测试结果。

    2118de6a090380772fb7dc803bc666df.png

      总的来说,单线程测试下,在小于256KB的内存场景或多线程场景下分配(比如应用层发网络包),使用tcmalloc效率比较高。在256KB~1MB中等内存分配上或大于1MB的大内存分配上,ptmalloc2分配要优于tcmalloc。而在多线程环境下,tcmalloc性能远远好于ptmalloc2。当然了,如果使用jemalloc,性能会比tcmalloc更好。

      ptmalloc2内存管理

     ptmalloc2统一管理heap和mmap区的chunk,避免了频繁的系统调用,一共维护了128个bin。使用数组来存储bin,所有的bin都是双向链表,bin上的每个元素就是一个chunk。

     此外,每次分配内存,ptmalloc2 一定要加锁,才能解决共享资源的互斥问题。因此在多线程环境下,ptmalloc分配效率容易低下。

    tcmalloc内存管理

    tcmalloc为每个线程分配了一个线程本地ThreadCache,小内存从ThreadCache分配,此外还有个中央堆(CentralCache),ThreadCache不够用的时候,会从CentralCache中获取空间放到ThreadCache中。

    一般小对象(<=32KB)从ThreadCache分配,大对象从CentralCache分配。大对象分配的空间都是4k页面对齐的,多个pages也能切割成多个小对象划分到ThreadCache中。

     小对象几乎无锁, >32KB的对象从CentralCache中分配使用自旋锁。并且>32KB对象都是页面对齐分配,多线程的时候应尽量避免频繁分配,否则也会造成自旋锁的竞争和页面对齐造成的浪费。更多细节也可以去tcmalloc的github看看源码:https://github.com/google/tcmalloc。

    jemalloc内存管理

    与tcmalloc类似,每个线程同样在<32KB的时候无锁使用线程本地cache。熟悉Netty的朋友,实际上Netty里面的内存池也是类似于jemalloc的实现方式,只是其中还划分为q25,q50等利用率进行选择。

    Jemalloc在64bits系统上使用下面的size-class分类:Small、Large、Huge。huge对象通过全局红黑树在对数时间内查找。

     虚拟内存被逻辑上分割成chunks(默认是4MB,1024个4k页),应用线程通过round-robin算法在第一次malloc的时候分配arena, 每个arena都是相互独立的,维护自己的chunks, chunk切割pages到small/large对象。free()的内存总是返回到所属的arena中,而不管是哪个线程调用free()。当然也可以去jemalloc官方去看看更多细节:http://jemalloc.net/。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值