【系列三:DevKit性能分析工具】第二讲:手把手带你找出程序中加锁范围不合理的代码
【系列三:DevKit性能分析工具】第二讲:手把手带你找出程序中加锁范围不合理的代码
如何学习性能优化
性能指标是什么?
高并发”和“响应快”一定是最先出现在你脑海里的两个词,而它们也正对应着性能优化的两个核心指标-“吞吐”和“延时”这两个指标是从应用负载的视角来考察性能,直接影响了产品终端的用户体验。跟它们对应的,是从系统资源的视角出发的指标,比如资源使用率、饱和度等。
想要学习好性能分析和优化,建立整体系统性能的全局观是最核心的话题。因而:
- 理解最基本的几个系统知识原理 (cpu、内存、存储l0、网络IO) ;
- 掌握必要的性能工具;
- 通过实际的场景演练,贯穿不同的组件。
如何学习性能优化?
其实说到性能工具,就不得不提性能领域的大师布伦丹·格雷格(Brendan Gregg)他不又是动态追踪工具DTrace的作者,还开发了许许多多的性能工具。我相信你一定见过他所描绘的 Linux性能工具图谱(其实,在上一讲已经展示过了,哈哈):
安装DevKit性能分析工具
调优前的准备工作
因为我们使用的是远程实验室的资源,已经安装好了一切,就直接用就好了。
操作流程
- 首先,登录鲲鹏性能分析工具,登录界面如下图所示:
登录成功后,可看到如下四个界面:
可以看到,鲲鹏性能分析工具由四个子工具组成,分别为:系统性能分析、Java性能分析、系统诊断和调优助手:
系统性能分析
在软件运行状态下,通过采集系统数据,可视化分析出系统性能指标,精准定位到瓶颈点及热点函数,提供一站式分析报告、多维度数据关联及优化建议。
Java性能分析
针对服务器上运行的Java程序,图形化显示Java程序的堆、线程、锁、垃圾回收等信息,收集热点函数,定位性能瓶颈点,帮助用户采取针对性优化。
系统诊断
针对基于鲲鹏的服务器的性能分析工具,提供内存泄漏诊断(包括内存未释放和异常释放)、内存越界诊断、内存消耗信息分析展示、OOM诊断能力,帮助用户识别出源代码中内存使用的问题点,提升程序的可靠性;压测网络,获得网络最大能力,为网络IO性能优化提供基础参考数据;诊断网络,定位网络疑难问题,解决因网络配置和异常而导致的网络IO性能问题;压测存储IO,获得存储设备最大能力,包括:吞吐量、IOPS、时延等,并以此评估存储能力,为存储IO性能优化提供基础参考数据。
调优助手
针对基于鲲鹏的服务器的调优工具,能系统化组织性能指标,引导用户分析性能瓶颈,实现快速调优。
- 新建分析任务
该界面如下图所示,可根据我们实际需要创建想要的分析任务:
可以看到这里有8个分析类型,具体选择要综合全局考虑。如果不知道当前程序瓶颈所在,就可以先使用通用分析来找症结,之后就可以针对性选择对应的系统部件分析和专项分析了。
- 全景分析任务
下面介绍下全景分析任务,首先接入总览界面,在这里,会展示具体的各种运行和性能的信息,还会给出优化建议和修改方法,可以按照建议试试,如下图所示:
下面是PCle拓扑信息,如果是物理机,会显示地更加详细和具体地信息:
接下来是性能,如图所示,可以很详细地展示四大组件的相关信息,支持表格和图格式展示,非常方便,我们主要看系统态和内核态使用情况、IO、软中断和硬中断的使用情况。
以上只是很粗略的分析,因为这里的信息很多,所以实际要针对具体任务进行分析,并不断尝试,需要付出一定努力。
下面,我们主要介绍下热点函数分析。采用热点函数分析,确定哪些热点函数存在性能瓶颈。主要介绍以下几点问题:
IDR指令比例高
CPU将内存中的数据读到CPU的高速缓存cache时,除了读取本次要访问的数据,还会预取本次数据的周边数据到cache里面,如果预取的数据是下次要访问的数据,那么性能会提升;如果预取的数据不是下次要取的数据,那么会浪费内存带宽。
对于数据比较集中的场景,预取的命中率高,适合打开CPU预取;若数据不集中,预取命中率低,则浪费内存带宽。
开启CPU Prefetching.
内联函数
频繁调用的小函数优化成内联函数,内联函数是典型的空间换时间的优化方法。参考链接:https://www.hikunpeng.com/document/detail/zh/kunpengdevps/hypertuner/hypertuner-tuner/kunpengtuning_12_0085.html
NEON指令
采用NEON指令(向量化)优化。参考链接:https://www.hikunpeng.com/document/detail/zh/kunpengdevps/hypertuner/hypertuner-tuner/kunpengtuning_12_0053.html
循环优化
循环优化是对程序中使用到的循环部分进行代码优化,合理的优化可以充分利用处理器的计算单元,提升指令流水线的调度效率,也可以提升cache命中率。循环优化的方法有很多,如循环展开、循环融合、循环分离、循环交换和循环平铺。
- 循环展开
循环展开(loop unrolling)牺牲函数的尺寸,降低循环带来的开销,加快程序执行速度,是一种典型的空间换时间方法。
针对Kunpeng 920处理器具有多个功能单元(FSUx2,ALUx3,LD/STx2)的特点,循环展开也有利于充分使用功能单元进行指令级并行,优化指令流水线的调度。
使用方法:针对循环之间不存在循环依赖或访问冲突的场景,可以使用SIMD改写、手工循环展开或者在编译选项中添加-funroll-loops(该选项是针对所有循环,建议只对热点函数手工循环展开);针对循环之间存在依赖或访问冲突的场景,可以考虑手工循环展开的方式,减少循环开销。
特别说明:小循环内部没有判断逻辑,收益较高; 大循环展开有可能会引起通用寄存器的溢出,降低性能(寄存器重命名/外溢至内存); 内部有判断逻辑可能会增加分支预测的开销,需要具体情况具体分析。
- 循环融合
循环融合(loop fusion)对于小循环体,可以考虑将函数上下流中的多个循环合并至一个循环内执行,减少对循环变量的操作;合并小循环后也有利于增加Kunpeng处理器乱序执行的机会,提升指令级的并行能力。
- 循环分离
循环分离(loop splitting) 针对较为复杂或计算密集的循环,可以将大循环拆分至多个小循环内执行,可以提升寄存器的利用效率;拆分大循环与合并小循环的使用是相对概念,难以绝对而言。 拆分成两个循环后,IPC得到了提升。
- 循环置换
循环置换(loop permutation) 在C语言中,多维数组的数据是按行存储的,在访问下列数据结构的数组元素时,使用循环置换改变顺序,可以有效减少时间。
- 循环平铺
循环平铺(loop tiling) 将一个循环拆分成一组嵌套循环,每个内部循环负责一个小数据块。下图是一个矩阵乘法的代码,通过将矩阵分为大小为blk*blk的小矩阵,在内层先做小矩阵的乘法,外层做分块矩阵的乘法,可以更好地提升cache利用率。参考链接:https://www.hikunpeng.com/document/detail/zh/kunpengdevps/hypertuner/hypertuner-tuner/kunpengtuning_12_0083.html
Cache优化
- cacheline对齐
- 消除伪共享
伪共享是指多核的多个私有变量位于同一个cacheline内,由于每个核修改变量时,会将其它核的整个cacheline无效掉,这样就会造成该cacheline在不同的核频繁迁移。这种现象类似共享变量的读写,但又不是真正的共享变量,故称伪共享。如图为伪共享所示。
如上图所示,CPU0与CPU1的私有变量刚好位于同一个cacheline内(私有变量分别对应红色块与蓝色块),CPU0修改其私有变量,会将整个cacheline无效,CPU1要访问其私有变量又需要重新从内存中读取,效率降低。可采用如下优化方案:
OpenMP代码中使用reduction子句替代直接写入共享变量(循环过程中写入线程私有变量)。
线程私有的变量按照cacheline大小对齐(线程栈上的变量除外)。
使用线程私有变量(如GCC支持__thread,C11支持_Thread_local关键字)。
多核优化之减少跨NUMA访问
鲲鹏处理器为乐高架构,每个处理器Socket内部拥有两个NUMA Node。线程/进程实际运行所在的物理核与内存NUMA Node位置关系,会带来访存路径时延的差异,本地访问性能最佳,本Socket内跨一次NUMA次之,跨Socket访问性能最不理想。
NUMA节点配置如下图所示:
可采用优化方案:
- 避免线程在运行过程中迁移:使用OpenMP时,通过配置环境变量OMP_PROC_BIND=true及OMP_PLACES指定线程要绑定的CPU核。如下图所示。
- 用numactl工具、taskset、cgroup/cpuset工具绑定进程/线程的位置关系。具体如下图所示。
其实,还有很多很多,很难一一展开。这都要求开发者对硬件架构、特性,操作系统和软件有较好的整体大局观,对于细节又要很了解,所以整体来说要求还是较高的,这也充分说明软硬协同优化的重要性啊。更多内容可到鲲鹏社区查看开发者文档。
- 点赞
- 收藏
- 关注作者
评论(0)