假如今天你那“变态”的老板又提出这样“恶心”的需求:核心业务系统,数据库性能再能有个30%~50%左右的提升。

请问这时你该怎么办?

要知道作为核心业务系统,通常运行的已经非常快了。

该调优的系统/数据库参数,该优化的SQL语句,该缩减的不必要事务SQL,应该都已经做了好几轮优化。

打开网易新闻 查看更多图片

所以,你双手插兜对老板说道:“老板,该优化的都优化了,没有空间了。”

老板:“小帅,你再想想呢?挤一挤,空间总是有的。”

你:“老板,AMD 刚推出了最新的 CPU 系列,性能较上一代能有30%的性能提升。”

老板:“钱,都是钱纳。现在的大环境不好。”

你:“老板,挤一挤,空间总是有的。”

老板:“嗯,这样吧,把你辞了,省下的钱可以买几台满配的服务器。空间这不就有了吗?“

你,卒......

编译器优化

今天,姜老师将告诉大家一个黑科技,在不修改任何一行代码的情况下,通过编译器优化,提升 MySQL 数据库的性能。

这种编译器优化称为:PGO(Profile Guided Optimization)。

根据姜老师的测试,通常性能可以有30%~50%的性能提升。

当然,在进行编译器优化前,你应该确保,例行该做的数据库调优都已经完成,否则 PGO 的优化也是毫无意义。

在 PGO 编译器优化前,我们做的比较多的编译器优化是分支预测优化。

这种奇技淫巧通过告诉编译器代码更倾向于走哪个分支,从而提升程序的运行效率。

例如在 MySQL 的源代码中我们可以看到类似这样的代码:

上述代码就是判断status这个变量大概率的值是REC_STATUS_ORDINARY,因此在编译阶段就会对上述代码进行优化,让 CPU 可以提前执行,从而提升效率。

宏 UNIV_EXPECT 就是对 GCC 提供的分支预测函数 __builtin_expect 进行的封装。

打开网易新闻 查看更多图片

除了函数__builtin_expect,GCC 还提供了分支预测函数 __builtin_prefetch 用于预读数据。

一旦预测是正确的,那么程序的性能就能得到有效的提升。

但分支预测优化存在以下几个问题:

1. 需要修改源码,而且需要非常有经验的程序员方能编写可靠的代码;

2. 编写的分支预测可能与实际业务运行的不一样,从而失去性能提升的可能性,反而导致灾难的发生;

为了解决上面这两个问题,可以使用 PGO,更为通用的编译器优化,从而提升程序的运行效率。

简单来说,PGO 会根据程序真实的运行情况,生成一份 profile。

下次编译时,通过上次运行产生的 profile 文件,以产生更加贴近真实运行情况、性能更好的代码。

可以看到,PGO 优化需要两次编译,三个阶段,即:

1、采样编译(Instrument):第1次编译,并向代码中插入指令,以便下一阶段可以收集数据;

2、训练程序(Train):运行程序,收集数据,产生 profile 文件;

3、优化编译(Optimization);第2次编译,利用程序实际运行产生的 profile 文件,对程序进行编译优化;

可以发现 PGO 优化不仅仅对数据库程序优化有效率,其实他是一种通用的优化手段,可以用于任何程序的优化。

常见的 C++、Rust语言都已支持 PGO 优化。

在 C++ 代码中使用 PGO 优化其实也比较简单,并不复杂。

加上编译参数-fprofile-generate用户采样编译,参数-fprofile-use表示用profile 进行优化编译。如:

# 采样编译g++ -o test test.cpp -fprofile-generate=/tmp/pgo# 运行程序./test# 优化编译g++ -o test test.cpp -fprofile-use=/tmp/pgo

由于 MySQL 数据库使用 C++ 进行编写,显然可以使用 PGO 优化。

而从 MySQL 8.0.19 版本开始,MySQL 的 cmake 文件支持 PGO 优化,感兴趣小伙伴可以阅读源码:fprofile.cmake

所以,对 MySQL 进行采样编译可以加上参数FPROFILE_GENERATE:

cmake .. -DFPROFILE_GENERATE=ON

接着运行采样编译的二进制程 序 mysqld,并运行 sysbench 程序,测试点查询的只读性能:

[ 16s ] thds: 16 tps: 63718.66 qps: 63718.66 (r/w/o: 63718.66/0.00/0.00) lat (ms,95%): 0.39 err/s: 0.00 reconn/s: 0.00[ 17s ] thds: 16 tps: 65274.25 qps: 65274.25 (r/w/o: 65274.25/0.00/0.00) lat (ms,95%): 0.34 err/s: 0.00 reconn/s: 0.00[ 18s ] thds: 16 tps: 65445.32 qps: 65445.32 (r/w/o: 65445.32/0.00/0.00) lat (ms,95%): 0.31 err/s: 0.00 reconn/s: 0.00[ 19s ] thds: 16 tps: 65178.78 qps: 65178.78 (r/w/o: 65178.78/0.00/0.00) lat (ms,95%): 0.32 err/s: 0.00 reconn/s: 0.00

这时,你会发现 MySQL 的 QPS 仅有 6.5W 左右。

这对于一台16核的云主机来说,性能表现也太差了。

但没关系,这是因为这时是在对 MySQL 数据库运行时的各种状态进行收集。

所以,若这时观察二进制文件所在的 xxx-profile-data 文件夹,你会发现该文件夹下已经产生了很多 gcda 结尾的 profile 文件。

$ ls build_release-profile-data#data#Projects#mysql-server#build_release#CMakeFiles#lz4_lib.dir#extra#lz4#lz4-1.9.4#lib#lz4.c.gcda#data#Projects#mysql-server#build_release#CMakeFiles#lz4_lib.dir#extra#lz4#lz4-1.9.4#lib#lz4frame.c.gcda#data#Projects#mysql-server#build_release#CMakeFiles#lz4_lib.dir#extra#lz4#lz4-1.9.4#lib#lz4hc.c.gcda#data#Projects#mysql-server#build_release#CMakeFiles#lz4_lib.dir#extra#lz4#lz4-1.9.4#lib#xxhash.c.gcda

接着,停止 mysqld 程序,通过下面的命令重新编译 MySQL:

cmake .. -DFPROFILE_USE=ON

通过指定FPROFILE_USE= ON,这时再次进行编译时,MySQL 会自动根据 xxx-profile-data 文件夹下的各种 profile 文件进行编译器优化。

这里产生的 mysqld 程序将会是对应上述 sysbench 测试后的编译优化版本。

因此,在优化编译后,重新启动优化后的 mysqld 程序,再进行同样的 sysbench 性能压测,结果为:

[ 20s ] thds: 16 tps: 367278.48 qps: 367278.48 (r/w/o: 367278.48/0.00/0.00) lat (ms,95%): 0.08 err/s: 0.00 reconn/s: 0.00[ 21s ] thds: 16 tps: 352094.84 qps: 352094.84 (r/w/o: 352094.84/0.00/0.00) lat (ms,95%): 0.08 err/s: 0.00 reconn/s: 0.00[ 22s ] thds: 16 tps: 356563.23 qps: 356563.23 (r/w/o: 356563.23/0.00/0.00) lat (ms,95%): 0.08 err/s: 0.00 reconn/s: 0.00[ 23s ] thds: 16 tps: 356433.08 qps: 356433.08 (r/w/o: 356433.08/0.00/0.00) lat (ms,95%): 0.08 err/s: 0.00 reconn/s: 0.00

这时 sysbench 性能的结果为 35.5W,较之前有明显的提升。

而如果不使用 PGO 优化,原版 MySQL 数据库的性能如下:

[ 20s ] thds: 16 tps: 255977.76 qps: 255977.76 (r/w/o: 255977.76/0.00/0.00) lat (ms,95%): 0.07 err/s: 0.00 reconn/s: 0.00[ 21s ] thds: 16 tps: 256667.17 qps: 256667.17 (r/w/o: 256667.17/0.00/0.00) lat (ms,95%): 0.07 err/s: 0.00 reconn/s: 0.00[ 22s ] thds: 16 tps: 246995.82 qps: 246995.82 (r/w/o: 246995.82/0.00/0.00) lat (ms,95%): 0.08 err/s: 0.00 reconn/s: 0.00[ 23s ] thds: 16 tps: 237734.88 qps: 237734.88 (r/w/o: 237734.88/0.00/0.00) lat (ms,95%): 0.12 err/s: 0.00 reconn/s: 0.00

可以看到,原版 MySQL 仅有 25W左右的 QPS。

简单来说,通过 PGO 优化后,性能提升超过 40%。

PGO优化总结

PGO 优化的效果非常不错,但是任何事物都有两面。

PGO 优化的最大问题在于:场景固定。

换句话说,PGO是一种基于场景反馈的优化。

如果你的场景既有 TP 查询,又有 AP 查询,而且各种业务都在上面跑,场景不单一,那么优化效果并不会特别好。

若你能清晰构造出业务的逻辑,读写的分布比例,事务的大小,那么 PGO 的优化将会非常突出。

就如我们前面看到的那样,可能会有 30% ~ 50% 的性能提升。

当今微服务架构下,可以越来越清楚地梳理出核心的业务逻辑,特别是对数据库的访问。

因此,姜老师非常推荐通过 PGO 这种最底层的编译器优化对数据库进行最后性能”榨干“的手段

最后,别忘了 PGO 不单单可以给数据库进行性能优化,其他程序也可以通过 PGO 受益,赶紧试试吧~~~

以上。