鲲鹏性能优化十板斧(二)——CPU与内存子系统性能调优
1.1 CPU与内存子系统性能调优简介
调优思路
性能优化的思路如下:
l 如果CPU的利用率不高,说明资源没有充分利用,可以通过工具(如strace)查看应用程序阻塞在哪里,一般为磁盘,网络或应用程序的业务处理中存在休眠或信号等待,这些优化措施在后续其它章节描述。
l 如果CPU利用率高,通过优化软件硬件的配置参数来更好适配业务场景,减少CPU占用率,让整个系统有更多的CPU时间来处理业务。
我们也可以选择更好的硬件,根据CPU的能力配置合适的内存条,建议内存满通道配置,发挥内存最大带宽:一颗鲲鹏920处理器的内存通道数为8,两颗鲲鹏920处理器的内存通道数为16;建议选择高频率的内存条,提升内存带宽:鲲鹏920在1DPC配置时,支持的内存最高频率为2933MHz。
主要优化参数
优化项 |
优化项简介 |
默认值 |
生效范围 |
鲲鹏916 |
鲲鹏920 |
优化应用程序的NUMA配置 |
在NUMA架构下,CPU core访问临近的内存时访问延迟更低。将应用程序绑在一个NUMA节点,可减少因访问远端内存带来的性能下降。 |
默认不绑定核 |
立即生效 |
yes |
yes |
修改CPU预取开关 |
内存预取在数据集中场景下可以提前将要访问的数据读到CPU cache 中,提升性能;若数据不集中,导致预取命中率低,则浪费内存带宽。 |
on |
重启生效 |
no |
yes |
调整定时器机制 |
nohz机制可减少不必要的时钟中断,减少CPU调度开销。 |
不同OS默认配置不同 Euler:nohz=off |
重启生效 |
yes |
yes |
调整内存的页大小为64K |
内存的页大小越大,TLB中每行管理的内存越多,TLB命中率就越高,从而减少内存访问次数。 |
不同OS默认配置不同: 4KB或64K |
重新编译内核、更新内核后生效 |
yes |
yes |
优化应用程序的线程并发数 |
适当调整应用的线程并发数,使得充分利用多核能力和资源争抢之间达到平衡。 |
由应用本身决定 |
立即生效或重启生效(由应用决定) |
yes |
yes |
1.2 常用性能监测工具
1.2.1 top工具
介绍
top是最常用的Linux性能监测工具之一。通过top工具可以监视进程和系统整体性能。
命令参考举例:
命令 |
说明 |
top |
查看系统整体的CPU、内存资源消耗。 |
top执行后输入1 |
查看每个CPU core资源使用情况。 |
top执行后输入F,并选择P选项 |
查看线程执行过程中是否调度到其它CPU core。 |
top -p $PID -H |
查看某个进程内所有线程的CPU资源占用。 |
安装方式
系统自带,无需安装。
使用方法
步骤 1 使用top命令统计整体CPU、内存资源消耗。
l CPU项:显示当前总的CPU时间使用分布。
-
us表示用户态程序占用的CPU时间百分比。
-
sy表示内核态程序所占用的CPU时间百分比。
-
wa表示等待IO等待占用的CPU时间百分比。
-
hi表示硬中断所占用的CPU时间百分比。
-
si表示软中断所占用的CPU时间百分比。
通过这些参数我们可以分析CPU时间的分布,是否有较多的IO等待。在执行完调优步骤后,我们也可以对CPU使用时间进行前后对比。如果在运行相同程序、业务情况下CPU使用时间降低,说明性能有提升。
l KiB Mem:表示服务器的总内存大小以及使用情况。
l KiB Swap:表示当前所使用的Swap空间的大小。Swap空间即当内存不足的时候,把一部分硬盘空间虚拟成内存使用。如果当前所使用的Swap空间大于0,可以考虑优化应用的内存占用或增加物理内存。
步骤 2 在top命令执行后按1,查看每个CPU core的使用情况。
通过该命令可以查看单个CPU core的使用情况,如果CPU占用集中在某几个CPU core上,可以结合业务分析触发原因,从而找到优化思路。
步骤 3 选中top命令的P选项,查看线程运行在哪些 CPU core上。
在top命令执行后按F,可以进入top命令管理界面。在该界面通过上下键移动光标到P选项,通过空格键选中后按Esc退出,即可显示出线程运行的CPU核。观察一段时间,若业务线程在不同NUMA节点内的CPU core上运行,则说明存在较多的跨NUMA访问,可通过NUMA绑核进行优化。
步骤 4 使用top -p $PID -H命令观察进程中每个线程的CPU资源使用。
“-p”后接的参数为待观察的进程ID。通过该命令可以找出消耗资源多的线程,随后可根据线程号分析线程中的热点函数、调用过程等情况
----结束
1.2.2 Perf工具
介绍
Perf工具是非常强大的Linux性能分析工具,可以通过该工具获得进程内的调用情况、资源消耗情况并查找分析热点函数。
命令参考举例:
命令 |
说明 |
perf top |
查看当前系统中的热点函数。 |
perf sched record -- sleep 1 -p $PID |
记录进程在1s内的系统调用。 |
perf sched latency --sort max |
查看上一步记录的结果,以调度延迟排序。 |
安装方式
以CentOS为例,使用如下命令安装:
# yum -y install perf
使用方法
步骤 1 通过perf top命令查找热点函数。
该命令统计各个函数在某个性能事件上的热度,默认显示CPU占用率,可以通过“-e”监控其它事件。
l Overhead表示当前事件在全部事件中占的比例。
l Shared Object表示当前事件生产者,如kernel、perf命令、C语言库函数等。
l Symbol则表示热点事件对应的函数名称。
通过热点函数,我们可以找到消耗资源较多的行为,从而有针对性的进行优化。
步骤 2 收集一段时间内的线程调用。
perf sched record命令用于记录一段时间内,进程的调用情况。“-p”后接进程号,“sleep”后接统计时长,单位为秒。收集到的信息自动存放在当前目录下,文件名为perf.data。
步骤 3 解析收集到的线程调度信息。
perf sched latency命令可以解析当前目录下的perf.data文件。“-s”表示进行排序,后接参数“max”表示按照最大延迟时间大小排序。
----结束
1.2.3 numactl工具
介绍
numactl工具可用于查看当前服务器的NUMA节点配置、状态,可通过该工具将进程绑定到指定CPU core,由指定CPU core来运行对应进程。
命令参考举例:
命令 |
说明 |
numactl -H |
查看当前服务器的NUMA配置。 |
numactl -C 0-7 ./test |
将应用程序test绑定到0~7核运行。 |
numastat |
查看当前的NUMA运行状态。 |
安装方式
以CentOS为例,使用如下命令安装:
# yum -y install numactl numastat
使用方法
步骤 1 通过numactl查看当前服务器的NUMA配置。
从numactl执行结果可以看到,示例服务器共划分为4个NUMA节点。每个节点包含16个CPU core,每个节点的内存大小约为64GB。同时,该命令还给出了不同节点间的距离,距离越远,跨NUMA内存访问的延时越大。应用程序运行时应减少跨NUMA访问内存。
步骤 2 通过numactl将进程绑定到指定CPU core。
通过 numactl -C 0-15 top 命令即是将进程“top”绑定到0~15 CPU core上执行。
可以通过numastat命令观察各个NUMA节点的状态。
l numa_hit表示节点内CPU核访问本地内存的次数。
l numa_miss表示节点内核访问其他节点内存的次数。跨节点的内存访问会存在高延迟从而降低性能,因此,numa_miss的值应当越低越好,如果过高,则应当考虑绑核。
----结束
1.3 优化方法
1.3.1 NUMA优化,减少跨NUMA访问内存
原理
通过1.1 鲲鹏处理器NUMA简介章节可以看到不同NUMA内的CPU core访问同一个位置的内存,性能不同。内存访问延时从高到低为:跨CPU > 跨NUMA不跨CPU > NUMA内
因此在应用程序运行时要尽可能的避免跨NUMA访问内存,我们可以通过设置线程的CPU亲和性来实现。
修改方式
l 网络可以通过如下方式绑定运行的CPU core,其中$cpuMask是16进制的数,最右边的bit表示core0;$irq为网卡队列中断号。
echo $cpuMask > /proc/irq/$irq/smp_affinity_list
l 通过numactl启动程序,如下面的启动命令表示启动test程序,只能在CPU core 28到core31运行(-C控制)。
numactl -C 28-31 ./test
l 在C/C++代码中通过sched_setaffinity函数来设置线程亲和性。
l 很多开源软件已经支持在自带的配置文件中修改线程的亲和性,例如nginx可以修改nginx.conf文件中的worker_cpu_affinity参数来设置nginx线程亲和性。
1.3.2 修改CPU的预取开关
原理
局部性原理分为时间局部性原理和空间局部性原理:
l 时间局部性原理(temporal locality):如果某个数据项被访问,那么在不久的将来它可能再次被访问。
l 空间局部性原理(spatial locality):如果某个数据项被访问,那么与其地址相邻的数据项可能很快也会被访问。
CPU将内存中的数据读到CPU的高速缓冲Cache时,会根据局部性原理,除了读取本次要访问的数据,还会预取本次数据的周边数据到Cache里面,如果预取的数据是下次要访问的数据,那么性能会提升,如果预取的数据不是下次要取的数据,那么会浪费内存带宽。
对于数据比较集中的场景,预取的命中率高,适合打开CPU预取,反之需要关闭CPU预取。目前发现speccpu和X265软件场景适合打开CPU预取,STREAM测试工具、Nginx和数据库场景需要关闭CPU预取。
修改方式
按照B 进入BIOS界面的步骤进入BIOS,然后在BIOS的如下位置设置CPU的预取开关。
1.3.3 定时器机制调整,减少不必要的时钟中断
原理
在Linux内核2.6.17版本之前,Linux内核为每个CPU设置一个周期性的时钟中断,Linux内核利用这个中断处理一些定时任务,如线程调度等。这样导致就算CPU不需要定时器的时候,也会有很多时钟中断,导致资源的浪费。Linux 内核2.6.17版本引入了nohz机制,实际就是让时钟中断的时间可编程,减少不必要的时钟中断。
修改方式
执行cat /proc/cmdline查看Linux 内核的启动参数,如果有nohz=off关键字,说明nohz机制被关闭,需要打开。修改方法如下:
修改前后,可以通过如下命令观察timer_tick的调度次数,其中$PID为要观察的进程ID,可以选择CPU占用高的进程进行观察:
perf sched record -- sleep 1 -p $PID
perf sched latency -s max
输出信息中有如下信息,其中591字段表示统计时间内的调度次数,数字变小说明修改生效。
timer_tick:(97) | 7.364 ms | 591 | avg: 0.012 ms | max: 1.268 ms
步骤 1 在“/boot”目录下通过find -name grub.cfg找到启动参数的配置文件。
步骤 2 在配置文件中将nohz=off去掉。
步骤 3 重启服务器。
----结束
1.3.4 调整内存页的大小为64K,提升TLB命中率
原理
TLB(Translation lookaside buffer)为页表(存放虚拟地址的页地址和物理地址的页地址的映射关系)在CPU内部的高速缓存。TLB的命中率越高,页表查询性能就越好。
TLB的一行为一个页的映射关系,也就是管理了一个页大小的内存:
TLB管理的内存大小 = TLB行数 x 内存的页大小
同一个CPU的TLB行数固定,因此内存页越大,管理的内存越大,相同业务场景下的TLB命中率就越高。
修改方式
修改Linux内核编译选项,并重新编译:
修改前后可以通过如下命令观察TLB的命中率($PID为进程ID):
perf stat -p $PID -d -d -d
输出结果包含如下信息,其中1.21%和0.59%分别表示数据的miss率和指令的miss率。
1,090,788,717 dTLB-loads # 520.592 M/sec
13,213,603 dTLB-load-misses # 1.21% of all dTLB cache hits
669,485,765 iTLB-loads # 319.520 M/sec
3,979,246 iTLB-load-misses # 0.59% of all iTLB cache hits
步骤 1 执行make menuconfig。
步骤 2 选择PAGESIZE大小为64K。
Kernel Features-->Page size(64KB)
步骤 3 编译和安装内核。
参考https://bbs.huaweicloud.com/forum/thread-24362-1-1.html
----结束
1.3.5 调整线程并发数
原理
程序从单线程变为多线程时,CPU和内存资源得到充分利用,性能得到提升。但是系统的性能并不会随着线程数的增长而线性提升,因为随着线程数量的增加,线程之间的调度、上下文切换、关键资源和锁的竞争也会带来很大开销。当资源的争抢比较严重时,甚至会导致性能明显降。下面数据为某业务场景下,不同并发线程数下的TPS,可以看到并发线程数达到128后,性能达到高峰,随后开始下降。我们需要针对不同的业务模型和使用场景做多组测试,找到适合本业务场景的最佳并发线程数。
修改方式
不同的软件有不同的配置,需要根据代码实现来修改,这里举例几个常用开源软件的修改方法:
-
MySql可以通过innodb_thread_concurrency设置工作线程的最大并发数。
-
Nginx可以通过worker_processes参数设置并发的进程个数。
作者:莱德汪汪队