参考: MySQL bug:https://bugs.mysql.com/bug.php?id=83047&tdsourcetag=s_pcqq_aiomsg
https://github.com/jemalloc/jemalloc/blob/dev/INSTALL.md
(1)ptmalloc 是glibc的内存分配管理
(2)tcmalloc 是google的内存分配管理模块
(3)jemalloc 是BSD的提供的内存分配管理 (可以使用jemalloc优化Nginx)
三者jemalloc和tcmalloc的性能不分伯仲,而ptmalloc则要低一些
1、下载最新版jemalloc
git clone https://github.com/jemalloc/jemalloc
cd jemalloc
[root@bogon jemalloc]# cat VERSION
5.1.0-108-gc4063ce439523d382f2dfbbc5bf6da657e6badb0
2、安装步骤:
./autogen.sh
./configure
make
make install
[root@bogon jemalloc]# make install
/usr/bin/install -c -d /usr/bin
/usr/bin/install -c -m 755 bin/jemalloc-config /usr/bin
/usr/bin/install -c -m 755 bin/jemalloc.sh /usr/bin
/usr/bin/install -c -m 755 bin/jeprof /usr/bin
/usr/bin/install -c -d /usr/include/jemalloc
/usr/bin/install -c -m 644 include/jemalloc/jemalloc.h /usr/include/jemalloc
/usr/bin/install -c -d /usr/lib
/usr/bin/install -c -m 755 lib/libjemalloc.so.2 /usr/lib
ln -sf libjemalloc.so.2 /usr/lib/libjemalloc.so
/usr/bin/install -c -d /usr/lib
/usr/bin/install -c -m 755 lib/libjemalloc.a /usr/lib
/usr/bin/install -c -m 755 lib/libjemalloc_pic.a /usr/lib
/usr/bin/install -c -d /usr/lib/pkgconfig
/usr/bin/install -c -m 644 jemalloc.pc /usr/lib/pkgconfig
/usr/bin/install -c -d /usr/share/doc/jemalloc
/usr/bin/install -c -m 644 doc/jemalloc.html /usr/share/doc/jemalloc
/usr/bin/install: cannot stat ‘doc/jemalloc.html’: No such file or directory
make: *** [install_doc_html] Error 1
[root@bogon jemalloc]#
默认安装目录:
PREFIX : /usr/local
BINDIR : /usr/local/bin
DATADIR : /usr/local/share
INCLUDEDIR : /usr/local/include
LIBDIR : /usr/local/lib
MANDIR : /usr/local/share/man
3、配置mysqld使用jemalloc,需要将配置写到[mysqld_safe] section
[mysqld_safe]
malloc-lib=/usr/local/lib/libjemalloc.so
4. 如果直接使用sqld启动,
export LD_PRELOAD=/usr/local/lib/libjemalloc.so
然后启动sqld
5、查看jemalloc是否生效
lsof -n |grep jemalloc
[root@localhost mysql3306]# lsof -n |grep jemalloc
bash 11223 root cwd DIR 253,0 4096 35178 /software/jemalloc
mysqld 17475 mysql mem REG 253,0 4291512 18480172 /usr/lib/libjemalloc.so.2
mysqld 17475 17482 mysql mem REG 253,0 4291512 18480172 /usr/lib/libjemalloc.so.2
mysqld 17475 17483 mysql mem REG 253,0 4291512 18480172 /usr/lib/libjemalloc.so.2
mysqld 17475 17484 mysql mem REG 253,0 4291512 18480172 /usr/lib/libjemalloc.so.2
显示以上信息说明mysql已经成功加载jemalloc
或者
使用jemalloc(或tcmalloc)优化MYSQL(安装步骤)
malloc
-
wget tar xjf jemalloc-3.4.0.tar.bz2 cd jemalloc-3.4.0 ./configure --prefix=/usr/local/jemalloc --libdir=/usr/local/lib make && make install echo '/usr/local/lib' > /etc/ld.so.conf.d/local.conf ldconfig
[root@host-192-168-1-56 mysql]# cp /usr/local/lib/libjemalloc.so /usr/lib64/mysql/libjemalloc.so
mysql使用jemalloc
- 修改配置文件
- [mysqld_safe]
malloc-lib=/usr/lib64/mysql/libjemalloc.so ###指定libjemalloc.so 即可
重启mysql检查是否生效
- [root@host-192-168-1-56 mysql]# lsof -n | grep jemalloc
mysqld 6032 mysql mem REG 252,1 4781206 320288 /usr/lib64/mysql/libjemalloc.so
MySQL使用Jemalloc
鉴于jemalloc的诸多优点,计划使用jemalloc作为内存管理器来优化MySQL,下面是测试环境。
测试环境
CPU: ARM64
Memory: 512GB
OS: CentOS Linux release 8.3.2011
Kernel: 4.18.0-193.28.1.el8_2.aarch64
MySQL: 8.0.25
Test Tool: SysBench 1.0.20
Jemalloc: 5.2.1
jemalloc的安装和使用
# wget https://github.com/jemalloc/jemalloc/archive/refs/tags/5.2.1.tar.gz -O jemalloc-5.2.1.tar.gz
# tar xzvf jemalloc-5.2.1.tar.gz
# cd jemalloc-5.2.1
# ./autogen.sh
//安装到指定目录
# ./configure --prefix=/home/test-user/jemalloc-5.2.1-install
//编译并安装
# make; make install
//配置环境变量
# export LD_PRELOAD=/home/test-user/jemalloc-5.2.1-install/lib/libjemalloc.so
安装好MySQL后,通过如下命令检查jemalloc是否被正常使用(MySQL的安装请参考官方步骤,这里不再赘述)
# lsof -n |grep jemalloc
下图显示MySQL已经正常使用jemalloc
测试用例
异常问题
压测过程中发现内存使用“异常”:MySQL进程占用的物理内存超过了100GB。
不使用jemalloc切换回默认的glibc后,内存占用降低到了7GB,和以往的测试结果一致。
从测试结果看,使用jemalloc作为内存管理器时内存使用量激增,需要进一步分析原因。内存使用量是否合理?是否和架构相关?
问题分析
第一阶段分析
1. 复现“问题”
首先需要确定该“问题”是否在x86架构上也存在,是否和操作系统或内核版本相关。
为了快速验证以上疑问,在AWS上分别创建x86实例(m5)和arm64实例(m6g)进行测试, 并没有复现“问题”。这两个实例默认的操作系统是Amazon Linux 2,而本地测试时使用的是CentOS8,在m6g上安装CentOS8重新测试,“问题”复现。测试结果如下:
2. 对比分析
对比测试环境,分析它们不同点,我们发现该“问题”只有在内核page size是64KB时才会出现。
另外,根据前文介绍,jemalloc中extent会基于page size分配内存。而且,深入分析jemalloc代码后还发现有多个数据结构的内存分配都涉及到page size,比如size_class, bin, extents, arena等等。
页表在操作系统中作为最基础的内存分配结构,ARM64支持4K、16K、64K不同大小的页表,x86只支持4KB。而本地测试使用的ARM64 CentOS的默认page size就是64KB,所以初步判断该“问题”和page size的配置相关。
3. 解决方法
即然ARM64架构支持多种page size,而page size为4KB时没有出现问题,那么可以修改ARM64 CentOS8的内核默认的page size来解决该"问题"。
修改page size方法
由于内核当前页表大小只支持静态配置,不支持动态修改,所以需要重新编译内核。
修改方法如下:
- 在 https://www.kernel.org/ 获取需要的内核版本
-
解压并修改内核配置参数
# tar xf linux-x.x.x.tar.xz # cd linux-x.x.x # cp /boot/config-xxx .config # make menuconfig
在图形菜单中找到“Kernel Features-> Page size”,选择4KB并保存配置
-
编译并安装新的内核
# make -j # make modules_install # make install
-
重启进入新的内核,参看page size是否修改成功
# getconf PAGE_SIZE 4096
4. 验证
修改page size为4KB后重新测试,jemalloc内存使用量和glibc接近。测试结果如下:
5. 潜在问题
至此该“问题”似乎可以通过修改page size来解决。但是,如果用户仍然需要使用64KB的页表,该方法将不再适用。
实际上,jemalloc本身支持编译参数“--with-lg-page=16”,该参数可以使jemalloc在page size为4KB时复用多个页面来达到使用64KB页面的效果。
尝试在4KB page size的系统下加入该编译参数,并没有出现内存使用量激增的现象。
这说明除了page size,还有其他因素影响了jemalloc的内存分配,仍然需要进一步分析。
第二阶段分析
1. micro-benchmark
通过以上测试发现该“问题”和MySQL并没有直接关系。为了简化分析和复现过程,单独开发了一个micro-benchmark https://github.com/machuang1983/jemalloc_micro_benchmark
该程序用于建立多个线程,每个线程分配一定内存,程序运行过程中实时打印进程的内存使用情况。
通过micro-benchmark可以快速复现问题。测试结果显示,每新建一个线程就会消耗1GB左右的内存。测试结果如下:
再次简化测试,直接运行单线程程序,如sleep 100,进程就会占用1GB内存。
由此看见,jemalloc针对一个线程进行内存初始化分配时就会分配1GB内存。需要深入分析jemalloc具体的分配机制。
2. 深入分析jemalloc代码
按前文所述,jemalloc的内存分配涉及到多个数据结构,我们结合gdb单步执行来分析jemalloc代码,同时实时查看内存占用的变化,由此定位到关键代码。
调试过程中发现,base会基于默认的hugepage size分配内存,分配之后监控到内存使用量突然增大,具体代码在 https://github.com/jemalloc/jemalloc/blob/dev/src/base.c#L46-L49
继续搜索hugepage size相关代码,还发现另一处使用它来分配内存,代码在https://github.com/jemalloc/jemalloc/blob/master/src/arena.c#L2052
由此可见除了page size,hugepage size对jemalloc的内存分配也有影响。通常hugepage size比page size大得多,所以hugepage size的影响会更大。
3. hugepage
内存管理采用"分页机制",但是当运行内存需求量较大时,默认page大小的页面会导致较多的TLB miss和缺页中断,从而大大影响应用程序性能。所以,有些场景希望可以使用更大的内存页作为映射单位,因此引入了hugepage。
不同架构支持的hugepage size不同,见下表:
4. 解决方法
ARM64 CentOS在page size=64KB时,默认hugepage size是512MB,jemalloc的base会以512MB来分配内存,而当page size=4KB时,默认hugepage size是2MB。所以回顾前面的测试,修改page size后问题消失的主要原因是默认的hugepage size改变导致的。
默认hugepage size修改方法
-
修改启动参数“default_hugepagesz=2M”
ARM64支持多种hugepage size,可以使用hugepagesz启动参数进行调整,无需重新编译内核。-
永久修改
- Centos: Set default_hugepagesz=2M in /boot/grub2/grubenv file
- Ubuntu: Set default_hugepagesz=2M to GRUB_CMDLINE_LINUX in /etc/default/grub file, then run “update-grub”
-
临时修改
- 内核启动时,输入"e"进入修改启动选项界面,加入参数“default_hugepagesz=2M”,然后输入"ctrl+x"启动内核。
-
- jemalloc编译参数"--with-lg-hugepage=21"
jemalloc支持编译参数"--with-lg-hugepage=21",替代系统的默认的hugepage size为2MB。建议使用该方法。
5. 验证
修改默认hugepage size后测试结果(sysbench使用256线程压测)如下:
测试结果显示,将hugepage size改为2MB以后,jemalloc的内存使用情况和glibc接近。
总结
该"问题"和架构无关,jemalloc作为内存管理器,如果默认hugepage size较大,会导致软件占用较大的内存,jemalloc提供了编译参数"--with-lg-hugepage=21"来降低这个影响。
由于ARM64支持更多类型的page size和hugepage size,用以提升软件的性能。所以用户在ARM64系统上使用jemalloc时,需要关注默认的page size和hugepage size,并根据具体需求做出相应的调整。