【性能】tcmalloc 使用和原理

165 篇文章 55 订阅

目录

一. 安装

二. 使用

使用方法

对比测试

替换内建的malloc/free

三、原理

四、问题或质疑

为什么测试的TCMalloc不靠谱,性能反而差了

tcmalloc是尬尴的存在?

五、其他相似的库Jemalloc

内存优化总结:ptmalloc、tcmalloc和jemalloc

概述

需求

目标

现状

glibc ptmalloc2

ptmalloc原理

系统调用接口

多线程支持

ptmalloc内存管理

ptmalloc分配流程

ptmalloc的缺陷

tcmalloc

小对象分配

CentralCache分配管理

回收

tcmalloc的改进

性能对比

官方测试

github mysql优化

Jemalloc

jemalloc原理

jemalloc的优化

性能对比

官方测试

参考资料

总结


前言

bandaoyu 本文随时更新,链接:http://t.csdn.cn/ChbHP

一. 安装

  tcmalloc在gperftools之中,故想要使用tcmalloc,就得先安装gperftools。在linux下,其安装步骤如下:

  tar xzvf gperftools-2.1.tar.gz (或者用git clone https://github.com/gperftools/gperftools.git下载)

cd gperftools-2.1

./autogen.sh 

./configure --enable-frame-pointers

make

make install

  这里需要注意一点,在linux下,如果直接./configure,那么make时会报出编译错误:error Cannot calculate stack trace: will need to write for your environment。解决方法如上所示,在configure时加入选项--enable-frame-pointers。

  如果想定制化安装,请自行参阅gperftools的安装文档,即源码包中的INSTALL文件。

二. 使用

使用方法

  对于tcmalloc的使用,还是用程序来说明吧。

  tcmalloc_sample.cpp:

#include <iostream>
#include <google/tcmalloc.h>

int main()
{
    char *cp = (char *)tc_malloc(23 * sizeof(char));
    tc_free(cp);
    cp = NULL;
    return 0;
}

  注:以下的步骤都是以第一部分所述的安装方式为前提而进行的。

  1. 编译:g++ -o tcmalloc_sample -g tcmalloc_sample.cpp -ltcmalloc

  2. 运行: ./tcmalloc_sample

  如果遇到以下错误:

  error while loading shared libraries: libtcmalloc.so.4: cannot open shared object file: No such file or directory

  那么就需要为系统指定libtcmalloc的加载路径:

  1. echo /usr/local/lib > /etc/ld.so.conf.d/libtcmalloc.conf

  2. ldconfig

  现在运行./tcmallco_sample命令,就不会出现上面提到的错误了。

对比测试

tmtest.cpp

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <thread>
 
#define MAX_COUNT 1000*1000
using namespace std;

void fun(int i)
{
    //char *ptr = (char *)malloc(i);
    //free(ptr);
    char *ptr = new char[1];
    delete[] ptr;
}
 
void fun_thread(void )
{
    int i = 0;
    int j = 1;
 
    while(i++ < MAX_COUNT)
    {
        j ++;
        fun(j);
 
        if ( j > 1024 )
            j = 1;
    }
}
 
#define MSECOND 1000000
 
int main()
{
 
    struct timeval tpstart, tpend;
    float timeuse;
 
    gettimeofday(&tpstart, NULL);
 
    thread t(fun_thread);
    t.join();
   
    int i = 0;
    int j = 1;
 
    while(i++ < MAX_COUNT)
    {
 
        j ++;
        fun(i);
        if ( j > 1024 )
            j = 1;
        //usleep(1);
    }
 
    gettimeofday(&tpend, NULL);
 
    timeuse = MSECOND * (tpend.tv_sec - tpstart.tv_sec) + tpend.tv_usec - tpstart.tv_usec;
    timeuse /= MSECOND;
    printf("Used Time:%f\n", timeuse);
    return 0;
}

#正常

[root@localhost test]# g++ tmtest.cpp -o 1 -lpthread
[root@localhost test]# ./tmtest
Used Time:5.336594
#替换malloc
[root@localhost test]# g++ tmtest.cpp -o 1 -lpthread -ltcmalloc
[root@localhost test]# ./tmtest
Used Time:0.208050

三、如何替换libc中的malloc

如何替换libc中的malloc -https://zhuanlan.zhihu.com/p/387038698

libc中使用的malloc是在ptmalloc库,但如果我们希望使用tcmalloc库中的malloc和free来分配和释放内存时,我们应该如何做呢?接下来我们分析一下集中方法:

1、使用环境变量LD_PRELOAD

环境变量LD_PRELOAD指定了动态库链接的仅次于rpath(生成在可执行文件中:https://blog.csdn.net/bandaoyu/article/details/113181179)的最高优先级顺序,当我们将LD_PRELOAD设置为tcmalloc.so时:

$ LD_PRELOAD="/usr/lib/libtcmalloc.so"

那么tcmalloc.so中的符号将拥有最高优先级,那么他将比libc中的malloc和free先被链接,从而达到替换libc中malloc和free的效果。

2、编译动态库或可执行程序时带上tcmalloc库的选项

我们在编译时,我们直接指定tcmalloc这个库,例如:

$ gcc -o main main.cc -L. -ltcmalloc -Wl,-rpath,.

这样编译出来的可执行程序main使用的malloc和free将来自tcmalloc。

3、通过链接过程控制

链接器ld有一个编译选项-wrap,当需要查找某个符号xxx时,它会优先查找__wrap_xxx,如果解析不到__wrap_xxx时才会去查找xxx。例如,我们在foo.c中定义了如下函数:

// foo.c
#include <stdio.h>

void *__wrap_malloc(size_t sz) {
    printf("__wrap_malloc: size = %d\n", sz);
    return NULL;
}

void main() {
    malloc(10);
}

那么在编译时,所有对malloc符号的解析会优先查找__wrap_malloc符号。也就是说,所有对malloc的调用会被调用到__wrap_malloc这个函数上。

$ gcc foo.c -o foo -Wl,-wrap,malloc
$ ./foo
__wrap_malloc: size = 10

4、替换内建的malloc/free

在编译程序的时候,gcc添加下面的选项,

-fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free

参考资料:

1、TCMalloc : Thread-Caching Malloc--https://link.zhihu.com/?target=https%3A//gperftools.github.io/gperftools/tcmalloc.html

To use TCMalloc, just link TCMalloc into your application via the "-ltcmalloc" linker flag.
You can use TCMalloc in applications you didn't compile yourself, by using LD_PRELOAD:

   $ LD_PRELOAD="/usr/lib/libtcmalloc.so" 
LD_PRELOAD is tricky, and we don't necessarily recommend this mode of usage.

TCMalloc includes a heap checker and heap profiler as well.

If you'd rather link in a version of TCMalloc that does not include the heap profiler

四、原理

tcmalloc原理 - https://www.jianshu.com/p/7c55fbdef679

(原理简单概括:内存池要解决的问题之一就是频繁的申请与释放,一般是在预分配里先找,有合适的用合适的,没有合适的再向系统申请,然后记录这块内存,最开始的版本大部分是统一管理,后面分了线程本地,线程本地解决了同步用锁,效率更高了)

tcmalloc为什么快:

使用了thread cache(线程cache),小块的内存分配都可以从cache中分配,这样再多线程分配内存的情况下,可以减少锁竞争

tcmalloc将内存请求分为两类,大对象请求小对象请求,大对象为>=32K的对象。

tcmalloc会为每个线程分配本地缓存,小对象请求可以直接从本地缓存获取,如果没有空闲内存,则从central heap中一次性获取一连串小对象。大对象是直接使用页级分配器(page-level allocator)从Central page Heap中进行分配,即一个大对象总是按页对齐的。

tcmalloc对于小内存,按8的整数次倍分配,对于大内存,按4K的整数次倍分配。

当某个线程缓存中所有对象的总大小超过2MB的时候,会进行垃圾收集。垃圾收集阈值会自动根据线程数量的增加而减少,这样就不会因为程序有大量线程而过度浪费内存。

tcmalloc为每个线程分配一个thread-local cache,小对象的分配直接从thread-local cache中分配。根据需要将对象从CentralHeap中移动到thread-local cache,同时定期的用垃圾回收器把内存从thread-local cache回收到Central free list中。
链接:https://www.jianshu.com/p/7c55fbdef679

10、总结

tcmalloc的内存分配分为四层:

ThreadCache(用于小对象分配):线程本地缓存,每个线程独立维护一个该对象,多线程在并发申请内存时不会产生锁竞争。

CentralCache(Central free list,用于小对象分配):全局cache,所有线程共享。当thread cache空闲链表为空时,会批量从CentralCache中申请内存;当thread cache总内存超过阈值,会进行内存垃圾回收,将空闲内存返还给CentralCache。

Page Heap(小/大对象):全局页堆,所有线程共享。对于小对象,当centralcache为空时,会从page heap中申请一个span;当一个span完全空闲时,会将该span返还给page heap。对于大对象,直接从page heap中分配,用完直接返还给page heap。

系统内存:当page cache内存用光后,会通过sbrk、mmap等系统调用向OS申请内存。

tcmalloc内存分配与使用分析_weixin_30341745的博客-CSDN博客


背景:

今天对服务器进行压测,模拟的请求量到4万次/分的时候,进程的CPU占用就已经达到400%了(也就是把四个核都占到100%)。其实模拟的请求数据都是单一的,不会因为BUG的原因导致CPU过高。怀疑是代码里的STL用得过多,加之ttserver和memcached大量的读操作——大量的小内存分配可能带来全局的锁竞争,从而可能使得CPU过高。

之前听说过google的tcmalloc是一个很好的线程内的内存分配器,能够提高内存分配的性能。正好今天试试能不能改善我的代码的性能。
安装的过程如下:
#1、到google下载代码:
cd /home/ahfu/temp
wget https://gperftools.googlecode.com/files/gperftools-2.1.tar.gz

#解压
tar -zxvf google-perftools-1.4.tar.gz
#看看说明
cd google-perftools-1.4
./configure -h
#选择简单的安装
mkdir -p /home/ahfu/temp/tcmalloc
./configure --disable-cpu-profiler --disable-heap-profiler --disable-heap-checker --disable-debugalloc --enable-minimal --disable-shared
make && make install
#在makefile里面加入一行就行
LIB = "/home/ahfu/temp/tcmalloc/lib/libtcmalloc_minimal.a"
#  g++ .....   ${LIB}

再进行压测!
大吃一惊!
请求量模拟到28万次/分后,CPU占用还不到20%!!!

五、问题或质疑

为什么测试的TCMalloc不靠谱,性能反而差了

为什么测试的TCMalloc不靠谱,性能反而差了:https://bbs.csdn.net/topics/390368040?page=2

问题:发现确实在release下性能用了tcmalloc后提升很多时间缩短了100%以上,但是debug反而比不用tcmalloc的差。

答1:

这种测试代码是不靠谱的 因为这种申请后马上释放,再申请,可能会复用前一次的内存(释放给系统后,不会马上归还到虚拟/物理内存) 公平的测试方法是随机分配(随机大小)、随机释放 还有tcmalloc在多线程才显优势

答2:那是因为MSVC已经默认用上了内存池,tcmalloc的内存池效率不如ms的。

tcmalloc是尬尴的存在?

[C++] tcmalloc的尴尬_王绍全的博客-https://blog.csdn.net/wwwsq/article/details/7076481

“专用的‘对象池’可以比通用的‘内存池’性能高两个数量级。

通过宏定义(DECL_MEM_POOL, IMPL_MEM_POOL)可以很快速的为class增加pool能力,还可以在单线程的环境下去掉锁。

真正要性能的程序不会整天去分配内存。性能要求没那么高的程序glibc就够了。

所以,tcmalloc是一个很尴尬的东西。

ps:tcmalloc据说可以用来优化mysql(让mysql加载tcmalloc来代替glibc的相应函数)。不过mysql的性能瓶颈在于磁盘、索引、缓存,替换malloc能有多少作用很值得怀疑。”

其他相似的库Jemalloc

ptmalloc是glibc默认的malloc库。

Jemalloc和tcmalloc 区别:

《ptmalloc,tcmalloc和jemalloc内存分配策略研究》http://www.360doc.cn/mip/435803027.html

总结
    Jemalloc设计上比前两个复杂地多,其内部使用了红黑树管理分页和内存块。并且对内存分配粒度分类地更细。这导致一方面比ptmalloc的锁争用要少,另一方面很多索引和查找都能回归到指数级别,方便了很多复杂功能的实现。而且在大内存分配上,内存碎片也会比tcmalloc少。但是也正是因为他的结构比较复杂,记录了很多meta,所以在分配很多小内存的时候记录meta数据的空间会略微多于tcmalloc。但是又不像ptmalloc那样每一个内存块都有一个header,而采用全局的bitmap记录状态,所以大量小内存的时候,会比ptmalloc消耗的额外内存小。
 

大总结
    看这些个分配器的分配机制,可见这些内存管理机制都是针对小内存分配和管理。对大块内存还是直接用了系统调用。所以在程序中应该尽量避免大内存的malloc/new、free/delete操作。另外这个分配器的最小粒度都是以8字节为单位的,所以频繁分配小内存,像int啊bool啊什么的,仍然会浪费空间。经过测试无论是对bool、int、short进行new的时候,实际消耗的内存在ptmalloc和tcmalloc下64位系统地址间距都是32个字节。大量new测试的时候,ptmalloc平均每次new消耗32字节,tcmalloc消耗8字节(我想说ptmalloc弱爆啦,而且tcmalloc)。所以大量使用这些数据的时候不妨用数组自己维护一个内存池,可以减少很多的内存浪费。(原来STL的map和set一个节点要消耗近80个字节有这么多浪费在这里了啊)
    而多线程下对于比较大的数据结构,为了减少分配时的锁争用,最好是自己维护内存池。单线程的话无所谓了,呵呵。不过自己维护内存池是增加代码复杂度,减少内存管理复杂度。但是我觉得,255个分页以下(1MB)的内存话,tcmalloc的分配和管理机制已经相当nice,没太大必要自己另写一个。
    另外,Windows下内存分配方式不知道,不同类型(int、short和bool)连续new的地址似乎是隔开的,可能是内部实现的粒度更小,不同size的class更多。测试10M次new的时候,debug模式下明显卡顿了一下,平均每次new的内存消耗是52字节(32位)和72字节(64位)[header更复杂?]。但是Release模式下很快,并且平均每次new的内存消耗是20字节(32位)和24字节(64位)。可以猜测VC的malloc的debug模式还含有挺大的debug信息。是不是可以得出他的header里,Release版本只有1个指针,Debug里有5个指针呢?

更多详细文档:TCMalloc解密 - https://www.jianshu.com/p/11082b443ddf

内存优化总结:ptmalloc、tcmalloc和jemalloc

 发表于 2016-06-13

概述

需求

系统的物理内存是有限的,而对内存的需求是变化的, 程序的动态性越强,内存管理就越重要,选择合适的内存管理算法会带来明显的性能提升。
比如nginx, 它在每个连接accept后会malloc一块内存,作为整个连接生命周期内的内存池。 当HTTP请求到达的时候,又会malloc一块当前请求阶段的内存池, 因此对malloc的分配速度有一定的依赖关系。(而apache的内存池是有父子关系的,请求阶段的内存池会和连接阶段的使用相同的分配器,如果连接内存池释放则请求阶段的子内存池也会自动释放)。

目标

内存管理可以分为三个层次,自底向上分别是:

  • 操作系统内核的内存管理
  • glibc层使用系统调用维护的内存管理算法
  • 应用程序从glibc动态分配内存后,根据应用程序本身的程序特性进行优化, 比如使用引用计数std::shared_ptr,apache的内存池方式等等。
    当然应用程序也可以直接使用系统调用从内核分配内存,自己根据程序特性来维护内存,但是会大大增加开发成本。

本文主要介绍了glibc malloc的实现,及其替代品

一个优秀的通用内存分配器应具有以下特性:

  • 额外的空间损耗尽量少
  • 分配速度尽可能快
  • 尽量避免内存碎片
  • 缓存本地化友好
  • 通用性,兼容性,可移植性,易调试

现状

目前大部分服务端程序使用glibc提供的malloc/free系列函数,而glibc使用的ptmalloc2在性能上远远弱后于google的tcmalloc和facebook的jemalloc。 而且后两者只需要使用LD_PRELOAD环境变量启动程序即可,甚至并不需要重新编译。

glibc ptmalloc2

ptmalloc2即是我们当前使用的glibc malloc版本。

ptmalloc原理

系统调用接口

x86进程地址空间


上图是 x86_64 下 Linux 进程的默认地址空间, 对 heap 的操作, 操作系统提供了brk()系统调用,设置了Heap的上边界; 对 mmap 映射区域的操作,操作系 统 供了 mmap()和 munmap()函数。
因为系统调用的代价很高,不可能每次申请内存都从内核分配空间,尤其是对于小内存分配。 而且因为mmap的区域容易被munmap释放,所以一般大内存采用mmap(),小内存使用brk()。

多线程支持

  • Ptmalloc2有一个主分配区(main arena), 有多个非主分配区。 非主分配区只能使用mmap向操作系统批发申请HEAP_MAX_SIZE(64位系统为64MB)大小的虚拟内存。 当某个线程调用malloc的时候,会先查看线程私有变量中是否已经存在一个分配区,如果存在则尝试加锁,如果加锁失败则遍历arena链表试图获取一个没加锁的arena, 如果依然获取不到则创建一个新的非主分配区。
  • free()的时候也要获取锁。分配小块内存容易产生碎片,ptmalloc在整理合并的时候也要对arena做加锁操作。在线程多的时候,锁的开销就会增大。

ptmalloc内存管理

  • 用户请求分配的内存在ptmalloc中使用chunk表示, 每个chunk至少需要8个字节额外的开销。 用户free掉的内存不会马上归还操作系统,ptmalloc会统一管理heap和mmap区域的空闲chunk,避免了频繁的系统调用。
  • ptmalloc 将相似大小的 chunk 用双向链表链接起来, 这样的一个链表被称为一个 bin。Ptmalloc 一共 维护了 128 个 bin,并使用一个数组来存储这些 bin(如下图所示)。

    ptmalloc结构图


    数组中的第一个为 unsorted bin, 数组中从 2 开始编号的前 64 个 bin 称为 small bins, 同一个small bin中的chunk具有相同的大小。small bins后面的bin被称作large bins。

  • 当free一个chunk并放入bin的时候, ptmalloc 还会检查它前后的 chunk 是否也是空闲的, 如果是的话,ptmalloc会首先把它们合并为一个大的 chunk, 然后将合并后的 chunk 放到 unstored bin 中。 另外ptmalloc 为了提高分配的速度,会把一些小的(不大于64B) chunk先放到一个叫做 fast bins 的容器内。

  • 在fast bins和bins都不能满足需求后,ptmalloc会设法在一个叫做top chunk的空间分配内存。 对于非主分配区会预先通过mmap分配一大块内存作为top chunk, 当bins和fast bins都不能满足分配需要的时候, ptmalloc会设法在top chunk中分出一块内存给用户, 如果top chunk本身不够大, 分配程序会重新mmap分配一块内存chunk, 并将 top chunk 迁移到新的chunk上,并用单链表链接起来。如果free()的chunk恰好 与 top chunk 相邻,那么这两个 chunk 就会合并成新的 top chunk,如果top chunk大小大于某个阈值才还给操作系统。主分配区类似,不过通过sbrk()分配和调整top chunk的大小,只有heap顶部连续内存空闲超过阈值的时候才能回收内存。
  • 需要分配的 chunk 足够大,而且 fast bins 和 bins 都不能满足要求,甚至 top chunk 本身也不能满足分配需求时,ptmalloc 会使用 mmap 来直接使用内存映射来将页映射到进程空间。

ptmalloc分配流程

ptmalloc分配流程

ptmalloc的缺陷

  • 后分配的内存先释放,因为 ptmalloc 收缩内存是从 top chunk 开始,如果与 top chunk 相邻的 chunk 不能释放, top chunk 以下的 chunk 都无法释放。
  • 多线程锁开销大, 需要避免多线程频繁分配释放。
  • 内存从thread的areana中分配, 内存不能从一个arena移动到另一个arena, 就是说如果多线程使用内存不均衡,容易导致内存的浪费。 比如说线程1使用了300M内存,完成任务后glibc没有释放给操作系统,线程2开始创建了一个新的arena, 但是线程1的300M却不能用了。
  • 每个chunk至少8字节的开销很大
  • 不定期分配长生命周期的内存容易造成内存碎片,不利于回收。 64位系统最好分配32M以上内存,这是使用mmap的阈值。

tcmalloc

tcmalloc是Google开源的一个内存管理库, 作为glibc malloc的替代品。目前已经在chrome、safari等知名软件中运用。
根据官方测试报告,ptmalloc在一台2.8GHz的P4机器上(对于小对象)执行一次malloc及free大约需要300纳秒。而TCMalloc的版本同样的操作大约只需要50纳秒。

小对象分配

  • tcmalloc为每个线程分配了一个线程本地ThreadCache,小内存从ThreadCache分配,此外还有个中央堆(CentralCache),ThreadCache不够用的时候,会从CentralCache中获取空间放到ThreadCache中。
  • 小对象(<=32K)从ThreadCache分配,大对象从CentralCache分配。大对象分配的空间都是4k页面对齐的,多个pages也能切割成多个小对象划分到ThreadCache中。

    tcmalloc小对象类型链表


    小对象有将近170个不同的大小分类(class),每个class有个该大小内存块的FreeList单链表,分配的时候先找到best fit的class,然后无锁的获取该链表首元素返回。如果链表中无空间了,则到CentralCache中划分几个页面并切割成该class的大小,放入链表中。

CentralCache分配管理

  • 大对象(>32K)先4k对齐后,从CentralCache中分配。 CentralCache维护的PageHeap如下图所示, 数组中第256个元素是所有大于255个页面都挂到该链表中。

    tcmalloc-pageheap

  • 当best fit的页面链表中没有空闲空间时,则一直往更大的页面空间则,如果所有256个链表遍历后依然没有成功分配。 则使用sbrk, mmap, /dev/mem从系统中分配。
  • tcmalloc PageHeap管理的连续的页面被称为span.
    如果span未分配, 则span是PageHeap中的一个链表元素
    如果span已经分配,它可能是返回给应用程序的大对象, 或者已经被切割成多小对象,该小对象的size-class会被记录在span中
  • 在32位系统中,使用一个中央数组(central array)映射了页面和span对应关系, 数组索引号是页面号,数组元素是页面所在的span。 在64位系统中,使用一个3-level radix tree记录了该映射关系。

回收

  • 当一个object free的时候,会根据地址对齐计算所在的页面号,然后通过central array找到对应的span。
  • 如果是小对象,span会告诉我们他的size class,然后把该对象插入当前线程的ThreadCache中。如果此时ThreadCache超过一个预算的值(默认2MB),则会使用垃圾回收机制把未使用的object从ThreadCache移动到CentralCache的central free lists中。
  • 如果是大对象,span会告诉我们对象锁在的页面号范围。 假设这个范围是[p,q], 先查找页面p-1和q+1所在的span,如果这些临近的span也是free的,则合并到[p,q]所在的span, 然后把这个span回收到PageHeap中。
  • CentralCache的central free lists类似ThreadCache的FreeList,不过它增加了一级结构,先根据size-class关联到spans的集合, 然后是对应span的object链表。如果span的链表中所有object已经free, 则span回收到PageHeap中。

tcmalloc的改进

  • ThreadCache会阶段性的回收内存到CentralCache里。 解决了ptmalloc2中arena之间不能迁移的问题。
  • Tcmalloc占用更少的额外空间。例如,分配N个8字节对象可能要使用大约8N * 1.01字节的空间。即,多用百分之一的空间。Ptmalloc2使用最少8字节描述一个chunk。
  • 更快。小对象几乎无锁, >32KB的对象从CentralCache中分配使用自旋锁。 并且>32KB对象都是页面对齐分配,多线程的时候应尽量避免频繁分配,否则也会造成自旋锁的竞争和页面对齐造成的浪费。

性能对比

官方测试

测试环境是2.4GHz dual Xeon,开启超线程,redhat9,glibc-2.3.2, 每个线程测试100万个操作。

tcmalloc测试1


上图中可以看到尤其是对于小内存的分配, tcmalloc有非常明显性能优势。

tcmalloc测试2


上图可以看到随着线程数的增加,tcmalloc性能上也有明显的优势,并且相对平稳。

github mysql优化

https://github.com/blog/1422-tcmalloc-and-mysql

Jemalloc

jemalloc是facebook推出的, 最早的时候是freebsd的libc malloc实现。 目前在firefox、facebook服务器各种组件中大量使用。

jemalloc原理

  • 与tcmalloc类似,每个线程同样在<32KB的时候无锁使用线程本地cache。
  • Jemalloc在64bits系统上使用下面的size-class分类:
    Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]
    Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]
    Huge: [4 MiB, 8 MiB, 12 MiB, …]
  • small/large对象查找metadata需要常量时间, huge对象通过全局红黑树在对数时间内查找。
  • 虚拟内存被逻辑上分割成chunks(默认是4MB,1024个4k页),应用线程通过round-robin算法在第一次malloc的时候分配arena, 每个arena都是相互独立的,维护自己的chunks, chunk切割pages到small/large对象。free()的内存总是返回到所属的arena中,而不管是哪个线程调用free()。

jemalloc Arena Chunk layout


上图可以看到每个arena管理的arena chunk结构, 开始的header主要是维护了一个page map(1024个页面关联的对象状态), header下方就是它的页面空间。 Small对象被分到一起, metadata信息存放在起始位置。 large chunk相互独立,它的metadata信息存放在chunk header map中。

  • 通过arena分配的时候需要对arena bin(每个small size-class一个,细粒度)加锁,或arena本身加锁。
    并且线程cache对象也会通过垃圾回收指数退让算法返回到arena中。

    jemalloc Arena and thread cache layout

jemalloc的优化

  • Jmalloc小对象也根据size-class,但是它使用了低地址优先的策略,来降低内存碎片化。
  • Jemalloc大概需要2%的额外开销。(tcmalloc 1%, ptmalloc最少8B)
  • Jemalloc和tcmalloc类似的线程本地缓存,避免锁的竞争
  • 相对未使用的页面,优先使用dirty page,提升缓存命中。

性能对比

官方测试

jemalloc官方测试报告


上图是服务器吞吐量分别用6个malloc实现的对比数据,可以看到tcmalloc和jemalloc最好(facebook在2011年的测试结果,tcmalloc这里版本较旧)。

4.3.2 mysql优化
测试环境:2x Intel E5/2.2Ghz with 8 real cores per socket,16 real cores, 开启hyper-threading, 总共32个vcpu。 16个table,每个5M row。
OLTP_RO测试包含5个select查询:select_ranges, select_order_ranges, select_distinct_ranges, select_sum_ranges,

jemalloc mysql性能测试

可以看到在多核心或者多线程的场景下, jemalloc和tcmalloc带来的tps增加非常明显。

2. jason在多处理器下的一个实验


x轴是线程数,y轴是性能。
在单线程下,彼此相差不多,dlmalloc(Doug Lea’s malloc, been around forever)稍占优势。
在多线程下,dlmalloc和pkgmalloc性能急剧下降,而jemalloc在线程数和处理器数一致的时候,性能达到最高,线程数继续增加能保持稳定。 

参考资料

glibc内存管理ptmalloc源代码分析
Inside jemalloc
tcmalloc浅析
tcmalloc官方文档
Scalable memory allocation using jemalloc
mysql-performance-impact-of-memory-allocators-part-2
ptmalloc,tcmalloc和jemalloc内存分配策略研究
Tick Tock, malloc Needs a Clock

总结

在多线程环境使用tcmalloc和jemalloc效果非常明显。
当线程数量固定,不会频繁创建退出的时候, 可以使用jemalloc;反之使用tcmalloc可能是更好的选择。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值