MySQL内存分配优化

2020-12-23  本文已影响0人  mysia

0 - 概述

系统的物理内存是有限的,而对内存的需求是变化的, 程序的动态性越强,内存管理就越重要,选择合适的内存管理算法会带来明显的性能提升。

MySQL作为常用的数据库,会有大量的内存操作。每次处理一个请求的时候,会在内存中操作数据,不断的进行malloc和free操作。因此,malloc的性能越好,MySQL的处理速度越快。

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

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

1- 现状

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

1.1 - glibc ptmalloc2

ptmalloc2即是glibc使用的malloc版本。

ptmalloc2

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

多线程支持:

ptmalloc内存管理:

ptmalloc分配流程

ptmalloc分配流程

ptmalloc的缺陷

1.2 - tcmalloc

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

小对象分配

tcmalloc

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

CentralCache分配管理

CentralCache

回收

tcmalloc的改进

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

测试1
上图中可以看到尤其是对于小内存的分配, tcmalloc有非常明显性能优势。
测试2
上图可以看到随着线程数的增加,tcmalloc性能上也有明显的优势,并且相对平稳。

github使用tcmalloc后,mysql性能提升30%

1.3 - Jemalloc

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

jemalloc原理

Jemalloc

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

jemalloc

jemalloc的优化

性能对比
官方测试

测试

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

MySQL测试

可以看到在多核心或者多线程的场景下, jemalloc和tcmalloc带来的tps增加非常明显。当线程数量固定,不会频繁创建退出的时候, 可以使用jemalloc;反之使用tcmalloc可能是更好的选择。

3 - MySQL替换malloc

下载对应版本的Jemalloc,解压、编译。

./configure --prefix=/usr/local/jemalloc --libdir=/usr/lib
make && make install
echo /usr/lib >> /etc/ld.so.conf
ldconfig

修改MySQL配置:

[mysqld_safe]
malloc-lib=/usr/lib/libjemalloc.so

启动MySQL,查看是否生效:

lsof -n | grep libjemalloc.so

4 - Jemalloc性能测试

通过一个简单的内存分配、释放程序进行,共分为三种情况:

#include <stdio.h>
#include<time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#ifdef USE_JEMALLOC
#include <jemalloc.h>
#endif

#define MSECOND 1000000

int main(void)
{
    int i = 0;
    char pad = 0;
    char *ptr = NULL;
    size_t size = 128;
    float timeuse = 0;
    struct timeval tpstart;
    struct timeval tpend;
    int loops = 100000000;

    srand((int)time(0));

    gettimeofday(&tpstart, NULL);
    for (i = 0; i < loops; i++) {
        size = rand() & 0x0000000000000fff;
        ptr = malloc(size);
        pad = rand() & 0xff;
        memset(ptr, pad, size);
        free(ptr);
    }
    gettimeofday(&tpend, NULL);

    timeuse=MSECOND*(tpend.tv_sec-tpstart.tv_sec)+tpend.tv_usec-tpstart.tv_usec;
    timeuse/=MSECOND;
    printf("Used Time [%f] with [%d] loops\n", timeuse, loops);

    return 0;
}

根据前文说的三种情况,共需要编译出两种二进制文件:

gcc memory.c -o a1.out -DUSE_JEMALLOC -O2 -ljemalloc
gcc memory.c -o a2.out -O2

得到a1.out为显示使用jemalloc的版本,a2.out为正常版本。

测试方法:

  1. 直接运行a1.out,可以得出显式使用jemalloc时的执行时间;
  2. 直接运行a2.out,可以得到使用普通malloc时的执行时间;
  3. 通过LD_PRELOAD环境变量执行a2.out,可以得到普通程序使用jemalloc时的执行时间;
./a1.out
./a2.out
LD_PRELOAD="/PATH/TO/libjemalloc.so" ./a2.out

得到三组数据如下:

Used Time [8.849940] with [100000000] loops
Used Time [11.283691] with [100000000] loops
Used Time [8.637403] with [100000000] loops

可以看到,在申请和释放随机大小的内存块时,使用jemalloc的性能明显好于普通malloc,性能提升约27.5%。

如果将程序源码中的size = rand() & 0x0000000000000fff;一行注释掉,让程序申请固定大小的内存块,得到三组数据:

Used Time [2.380698] with [100000000] loops
Used Time [4.532686] with [100000000] loops
Used Time [2.369266] with [100000000] loops

这种情况下使用jemalloc比普通malloc版本的性能提升将近一倍,同时比各自的随机大小内存块版本提升大约也有三四倍。

5 - 总结

无论是google的tcmalloc,还是facebook的jemalloc,在高并发的场景下对MySQL的TPS提升均在30%左右。而MySQL默认使用的glibc的malloc,不仅内存操作处理的效率低,也有OOM的风险。在条件允许的情况下,可以替换,推荐使用Jemalloc。

完。

上一篇下一篇

猜你喜欢

热点阅读