程序员性能之道,从使用perf开始!

news2025/1/10 1:47:00

一、perf简介

从2.6.31内核开始,Linux内核自带了一个性能分析工具perf,能够进行函数级与指令级的热点查找。通过它,应用程序可以利用 PMU,tracepoint 和内核中的特殊计数器来进行性能统计。它不但可以分析指定应用程序的性能问题 (per thread),也可以用来分析内核的性能问题,当然也可以同时分析应用代码和内核,从而全面理解应用程序中的性能瓶颈。

Perf是内置于Linux内核源码树中的性能剖析(profiling)工具。它基于事件采样原理,以性能事件为基础,支持针对处理器相关性能指标与操作系统相关性能指标的性能剖析,常用于性能瓶颈的查找与热点代码的定位。

1.1安装Perf

安装 perf 非常简单, 只要内核版本高于2.6.31的, perf已经被内核支持. 首先安装内核源码:

apt-get install linux-source

那么在 /usr/src 目录下就已经下载好了内核源码, 我们对源码包进行解压, 然后进入 tools/perf 目录然后敲入下面两个命令即可:

make
make install

可能因为系统原因, 需要提前安装下面的开发包:

apt-get install -y binutils-dev
apt-get install -y libdw-dev
apt-get install -y python-dev
apt-get install -y libnewt-dev

1.2Perf的基本使用

CPU周期(cpu-cycles)是默认的性能事件,所谓的CPU周期是指CPU所能识别的最小时间单元,通常为亿分之几秒,是CPU执行最简单的指令时所需要的时间,例如读取寄存器中的内容,也叫做clock tick。

perf COMMAND [-e event …] PROGRAM, perf 是采用的这么一个命令格式, COMMAND一般常用的就是 top, stat, record, report等. 然后用 -e 参数来统计需要关注的事件. 多个事件就用多个 -e 连接。

Perf是一个包含22种子工具的工具集,以下是最常用的5种:

  • perf-list
  • perf-stat
  • perf-top
  • perf-record
  • perf-report
  • perf-trace

perf-list

Perf-list用来查看perf所支持的性能事件,有软件的也有硬件的。
List all symbolic event types。

perf list [hw | sw | cache | tracepoint | event_glob]

perf stat

说明一个工具的最佳途径是列举一个例子。考查下面这个例子程序。其中函数 longa() 是个很长的循环,比较浪费时间。函数 foo1 和 foo2 将分别调用该函数 10 次,以及 100 次。

//t1.c
 void longa()
 {
   int i,j;
   for(i = 0; i < 1000000; i++)
   j=i; //am I silly or crazy? I feel boring and desperate.
 }

 void foo2()
 {
   int i;
   for(i=0 ; i < 10; i++)
        longa();
 }

 void foo1()
 {
   int i;
   for(i = 0; i< 100; i++)
      longa();
 }

 int main(void)
 {
   foo1();
   foo2();
 }

然后编译它:

gcc -o t1 -g t1.c

下面演示了 perf stat 针对程序 t1 的输出:

root@ubuntu-test:~# perf stat ./t1

 Performance counter stats for './t1':

        218.584169 task-clock # 0.997 CPUs utilized
                18 context-switches # 0.000 M/sec
                 0 CPU-migrations # 0.000 M/sec
                82 page-faults # 0.000 M/sec
       771,180,100 cycles # 3.528 GHz
     <not counted> stalled-cycles-frontend
     <not counted> stalled-cycles-backend
       550,703,114 instructions # 0.71 insns per cycle
       110,117,522 branches # 503.776 M/sec
             5,009 branch-misses # 0.00% of all branches

       0.219155248 seconds time elapsed

程序 t1 是一个 CPU bound 型,因为 task-clock-msecs 接近 1

对 t1 进行调优应该要找到热点 ( 即最耗时的代码片段 ),再看看是否能够提高热点代码的效率。缺省情况下,除了 task-clock-msecs 之外,perf stat 还给出了其他几个最常用的统计信息:

  • Task-clock-msecs:CPU 利用率,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO。
  • Context-switches:进程切换次数,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的。
  • Cache-misses:程序运行过程中总体的 cache 利用情况,如果该值过高,说明程序的 cache 利用不好
  • CPU-migrations:表示进程 t1 运行过程中发生了多少次 CPU 迁移,即被调度器从一个 CPU 转移到另外一个 CPU 上运行。
  • Cycles:处理器时钟,一条机器指令可能需要多个 cycles,Instructions: 机器指令数目。
  • IPC:是 Instructions/Cycles 的比值,该值越大越好,说明程序充分利用了处理器的特性。
  • Cache-references: cache 命中的次数,Cache-misses: cache 失效的次数。

通过指定 -e 选项,您可以改变 perf stat 的缺省事件 ( 关于事件,在上一小节已经说明,可以通过 perf list 来查看 )。假如您已经有很多的调优经验,可能会使用 -e 选项来查看您所感兴趣的特殊的事件。

有些程序慢是因为计算量太大,其多数时间都应该在使用 CPU 进行计算,这叫做 CPU bound 型;有些程序慢是因为过多的 IO,这种时候其 CPU 利用率应该不高,这叫做 IO bound 型;对于 CPU bound 程序的调优和 IO bound 的调优是不同的。

perf top

使用 perf stat 的时候,往往您已经有一个调优的目标。比如我刚才写的那个无聊程序 t1。

也有些时候,您只是发现系统性能无端下降,并不清楚究竟哪个进程成为了贪吃的 hog。

此时需要一个类似 top 的命令,列出所有值得怀疑的进程,从中找到需要进一步审查的家伙。

Perf top 用于实时显示当前系统的性能统计信息。该命令主要用来观察整个系统当前的状态,比如可以通过查看该命令的输出来查看当前系统最耗时的内核函数或某个用户进程。

让我们再设计一个例子来演示吧,我很快就想到了如代码清单 2 所示的一个程序:

//t2.c
main(){
    int i;
    while(1) i++;
}

然后编译这个程序:

gcc -o t2 -g t2.c

运行这个程序后, 我们另起一个窗口,运行perf top来看看:

Events: 8K cycles
 98.67% t2 [.] main
  1.10% [kernel] [k] __do_softirq
  0.07% [kernel] [k] _raw_spin_unlock_irqrestore
  0.05% perf [.] kallsyms__parse
  0.05% libc-2.15.so [.] 0x807c7
  0.05% [kernel] [k] kallsyms_expand_symbol
  0.02% perf [.] map__process_kallsym_symbol

很容易便发现 t2 是需要关注的可疑程序。不过其作案手法太简单:肆无忌惮地浪费着 CPU。所以我们不用再做什么其他的事情便可以找到问题所在。但现实生活中,影响性能的程序一般都不会如此愚蠢,所以我们往往还需要使用其他的 perf 工具进一步分析。

使用 perf record, 解读 report

使用 top 和 stat 之后,您可能已经大致有数了。要进一步分析,便需要一些粒度更细的信息。比如说您已经断定目标程序计算量较大,也许是因为有些代码写的不够精简。那么面对长长的代码文件,究竟哪几行代码需要进一步修改呢?这便需要使用 perf record 记录单个函数级别的统计信息,并使用 perf report 来显示统计结果。

您的调优应该将注意力集中到百分比高的热点代码片段上,假如一段代码只占用整个程序运行时间的 0.1%,即使您将其优化到仅剩一条机器指令,恐怕也只能将整体的程序性能提高 0.1%。俗话说,好钢用在刀刃上,不必我多说了。

perf record -e cpu-clock ./t1
perf report

perf report 输出结果:

Events: 229 cpu-clock
100.00% t1 t1 [.] longa

不出所料,hot spot 是 longa( ) 函数。但,代码是非常复杂难说的,t1 程序中的 foo1() 也是一个潜在的调优对象,为什么要调用 100 次那个无聊的 longa() 函数呢?但我们在上图中无法发现 foo1 和 foo2,更无法了解他们的区别了。

我曾发现自己写的一个程序居然有近一半的时间花费在 string 类的几个方法上,string 是 C++ 标准,我绝不可能写出比 STL 更好的代码了。因此我只有找到自己程序中过多使用 string 的地方。因此我很需要按照调用关系进行显示的统计信息。

使用 perf 的 -g 选项便可以得到需要的信息:

perf record -e cpu-clock -g ./t1
perf report

输出结果:

Events: 270 cpu-clock
- 100.00% t1 t1 [.] longa
   - longa
      + 91.85% foo1
      + 8.15% foo2

通过对 calling graph 的分析,能很方便地看到 91.85% 的时间都花费在 foo1() 函数中,因为它调用了 100 次 longa() 函数,因此假如 longa() 是个无法优化的函数,那么程序员就应该考虑优化 foo1,减少对 longa() 的调用次数。

使用 tracepoint

当 perf 根据 tick 时间点进行采样后,人们便能够得到内核代码中的 hot spot。那什么时候需要使用 tracepoint 来采样呢?

我想人们使用 tracepoint 的基本需求是对内核的运行时行为的关心,如前所述,有些内核开发人员需要专注于特定的子系统,比如内存管理模块。这便需要统计相关内核函数的运行情况。另外,内核行为对应用程序性能的影响也是不容忽视的:

以之前的遗憾为例,假如时光倒流,我想我要做的是统计该应用程序运行期间究竟发生了多少次系统调用。在哪里发生的?

下面我用 ls 命令来演示 sys_enter 这个 tracepoint 的使用:

root@ubuntu-test:~# perf stat -e raw_syscalls:sys_enter ls
bin libexec off perf.data.old t1 t3 tutong.iso
bwtest minicom.log perf.data pktgen t1.c t3.c

 Performance counter stats for 'ls':

               111 raw_syscalls:sys_enter

       0.001557549 seconds time elapsed

个报告详细说明了在 ls 运行期间发生了多少次系统调用 ( 上例中有 111 次 )。

二、常见性能问题分析

性能测试大致分以下几个步骤:

  1. 需求分析
  2. 脚本准备
  3. 测试执行
  4. 结果整理
  5. 问题分析

需求描述:有一个服务,启动时会加载一个1G的词表文件到内存,请求来了之后,会把请求词去词表里做模糊匹配,如果匹配到了就向一个后端服务发送一条http请求,拿回数据之后,返回给客户端的同时,向mysql记录请求的唯一标识和一个请求次数的标记;

其中有几个关键函数

模糊匹配(fuzzyMatching)

后端请求函数(sendingRequest)

拼装请求函数(buildResponse)

记录mysql请求次数标记(signNum)

问题及分析:

第一组:完全随机请求词,qps达到1k时,服务器未见异常,cpu、内存、带宽均未满,qps无法继续提升;

  • 分析:由于此服务后端连接了其它服务,所以在压测之前,要确认后端服务不会成为瓶颈点,目前的状态很可能是后端服务限制了被测服务的性能;此时可以检查后端服务所在机器的各项指标,或者查看本机的连接状况,一般后端服务无法处理,而被测服务又会一直向后面请求的话,timewait状态的连接会变得比较多;

第二组:解决后端服务的问题后,第二组使用平均30个字的请求词,来打压,qps到400时,cpu load已满;

  • 分析:这种情况明显是由于fuzzyMatching函数计算效率的问题导致cpu满载,从而无法提升qps,使响应时间不断增大,此时可以通过perf+火焰图来确定整个处理请求过程中响应时间长的函数;此时需要评估压测数据是否合理,如果线上平均请求词只有2个的时候,此组测试明显不合理,此时要开发进行性能优化就是浪费时间的;如果评估测试数据合理,可以再次更换短词数据进行压测验证猜测;

第三组:解决了上述两个问题之后,使用完全随机请求词,qps到达3k后降低至1k,然后再次提升到3k,如此反复;

  • 分析:此时关注一下各项指标,排除了以上的问题的话,操作mysql慢的问题可能性大一些,对这种需要高并发的系统来说,直接读写mysql不是个聪明的解决方案,一般会用redis做一层缓存,这里说道的另一个问题就是开发设计不合理,导致的性能问题;

第四组:将后端换做真实的服务来做整体压测,发现qps最高只能到300,此时检查各项指标,发现入口带宽占满了;

  • 分析:这次问题比较明显,后端服务返回内容过大,导致带宽被占满,此时依然需要评估需求:1、是否需要后端返回的所有数据内容;2、评估更换万兆网卡的性价比;3、是否可以通过技术手段优化带宽占用,比如把一次请求分散到多组服务的多个请求;

2.1perf+火焰图定位函数问题

这里简单说一下如何使用perf+火焰图来直观的定位性能问题:

perf

Perf 拥有了众多的性能分析能力,举例来说,使用 Perf 可以计算每个时钟周期内的指令数,称为 IPC,IPC 偏低表明代码没有很好地利用 CPU。Perf 还可以对程序进行函数级别的采样,从而了解程序的性能瓶颈究竟在哪里等等。Perf 还可以替代 strace,可以添加动态内核 probe 点,还可以做 benchmark 衡量调度器的好坏。

  • 使用举例:perf record -e cpu-clock -g -p 11110 -o data/perf.data sleep 30
  • -g 选项是告诉perf record额外记录函数的调用关系 -e cpu-clock 指perf record监控的指标为cpu周期 -p 指定需要record的进程pid

生成火焰图

1、第一步:使用压力测试工具对程序进行打压,压到程序拐点;

$sudo perf record -e cpu-clock -g -p 11110
Ctrl+c结束执行后,在当前目录下会生成采样数据perf.data.

2、第二步:用perf 工具对perf.data进行解析

perf -i perf.data &> perf.unfold

3、第三步:将perf.unfold中的符号进行折叠:

./stackcollapse-perf.pl perf.unfold &> perf.folded

4、最后生成svg图:

./flamegraph.pl perf.folded > perf.svg

到这儿可以生成函数调用火焰图,如下图:

img

原生的perf可以直接定位C/C++的程序,通常编译debug版本的程序能看到更多的信息,java、go等语言可以通过各自定制的工具来生成,原理类似;通过火焰图可以轻松定位到哪个函数的处理时间最长,从而找到问题所在。


版权声明:本文为知乎博主「玩转Linux内核」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
4、最后生成svg图:

./flamegraph.pl perf.folded > perf.svg

到这儿可以生成函数调用火焰图,如下图:

[外链图片转存中…(img-etC4RSsA-1688132508735)]

原生的perf可以直接定位C/C++的程序,通常编译debug版本的程序能看到更多的信息,java、go等语言可以通过各自定制的工具来生成,原理类似;通过火焰图可以轻松定位到哪个函数的处理时间最长,从而找到问题所在。


版权声明:本文为知乎博主「玩转Linux内核」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接: https://zhuanlan.zhihu.com/p/638791429

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/704455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apikit 自学日记:使用脚本处理数据

脚本分为 前置脚本 和 后置脚本 两种&#xff0c;分别对应 API 请求前 和 返回数据后 的两个阶段。您可以通过编写 Javascript 代码&#xff0c;在 API 前置脚本中改变请求参数&#xff0c;或者是在 API 后置脚本中改变返回结果。 脚本常用于以下几种情况&#xff1a; API 请求…

【夜深人静学数据结构与算法 | 第十一篇】枚举算法

目录 前言&#xff1a; 枚举算法&#xff1a; 优点&#xff1a; 枚举算法的种类&#xff1a; 枚举算法案例&#xff1a; 343. 整数拆分 - 力扣&#xff08;LeetCode&#xff09; 12. 整数转罗马数字 - 力扣&#xff08;LeetCode&#xff09; 总结&#xff1a; 前言&…

video-05-videojs编写(全屏、非全屏)自定义控件!!!!

兄弟们&#xff01;&#xff01;看到这里&#xff0c;你马上就可以自定义控件了&#xff0c;想想是不是都激动啊&#xff0c;但是这篇文章重在思路及简单实现&#xff0c;仔细看。 目录 一、控件分类 二、实现方案&#xff08;方案二最好&#xff09; 2.1 方案1&#xff08;…

前端Vue自定义轮播图swiper 轮播图dot 轮播图指示indicate

前端Vue自定义轮播图swiper 轮播图dot 轮播图指示indicate&#xff0c;下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13193 效果图如下: ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​…

BACnet网关如何采集Modbus RTU设备转BACnet IP协议

BACnet标准是针对采暖、通风、空调、制冷控制设备设计的&#xff0c;同时也是为其他楼宇控制系统(例如照明&#xff0c;安保&#xff0c;消防等系统)的集成提供一个基本原则。 本文主要讲述了BACnet网关采集Modbus RTU设备&#xff08;M140T&#xff09;&#xff0c;将Modbus …

servlet拓展-统一响应json功能、全局异常处理

servlet拓展-统一响应json功能、全局异常处理 一、baseservlet import com.fasterxml.jackson.databind.ObjectMapper; import teamwork.com.utils.ApiResult;import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.Htt…

解决IDEA连接数据库后,xml中写SQL语句不提醒数据库表字段问题

有时候我们新建了一个项目&#xff0c;或者新建了一个工程&#xff0c;明明为此项目或者工程连接了数据库&#xff0c;并且一切正确&#xff0c;但是在xml中书写SQL语句就是不提示数据库表中的相关字段&#xff0c;无论是表名也好&#xff0c;还是字段名也好&#xff0c;通通不…

信号链噪声分析15

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示&#xff1a;这里可以添加技术概要 模数转换器(ADC)将模拟量——现实世界中绝大部分现象的特征——转换为数字语言&#xff0c; 以便用于信息处理、计算、数据传输和控制系统。数模转换器(DAC)则用于将发送或存 储…

信号链噪声分析17

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示&#xff1a;这里可以添加技术概要 只是快速阅读 HarryNyquist 于 1924 年发表的《贝尔系统技术杂志》经典文章&#xff0c;并不 足以了解以其名字命名的该项准则的真正意义。当时&#xff0c;Nyquist 正致力于研…

解放双手,保障企业安全这一点很重要!

无论是交通管理部门、工业企业还是其他需要保障安全的场所&#xff0c;远程烟雾监控系统都是一个可靠的选择。 这远程烟雾监控是一项重要的技术解决方案&#xff0c;通过使用先进的传感器和监控系统来实时监测和管理烟雾情况&#xff0c;以提高安全性并及时应对潜在的风险。 客…

强化学习从基础到进阶–案例与实践[11]:AlphaStar论文解读、监督学习、强化学习、模仿学习、多智能体学习、消融实验

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

H5工具:产品生命周期四阶段及其重点关注数据指标

人有生老病死&#xff0c;同样&#xff0c;一个产品也有生命周期&#xff0c;叫产品生命周期&#xff08;Product Life Cycle&#xff09;。 产品生命周期指的是产品从进入市场到退出市场的周期性变化过程&#xff0c;它是指产品的市场寿命&#xff0c;而非产品的使用寿命。 一…

Linux自主学习 - C语言库Melon的快速入门

备注&#xff1a;ubuntu-20.04.3-desktop-amd64.iso 一、C语言库melon的安装 1、选择一个合适的文件夹用于下载Melon&#xff0c;本文以~/ProjectCode为例 在~/ProjectCode下输入git clone https://github.com/Water-Melon/Melon.git 然后&#xff0c;可以使用ls命令查看到Mel…

青大数据结构【2017】【简答】

关键字&#xff1a; 链式存储、顺序查找、折半查找、归并排序 采用链式存储结构&#xff0c;原因&#xff1a; 1&#xff09;链式存储结构进行插入和删除只需要修改相应的指针就可以了&#xff0c;适合频繁的进行插入和删除操作。 2&#xff09;链式存储不要求地址连续&…

VUE elementUI 自定义组件效验器失效

首先看一段源码 只要给el-form-item 设置了prop&#xff0c;并且model有prop属性&#xff0c;根据此计算属性就会触发fieldValue的取值&#xff0c;从而使效验生效&#xff0c;也就是说&#xff0c;初始化属性一定得写。 其次可以利用手动触发效验的方法灵活控制。如下所示 v…

Learn Mongodb DB常用命令及数据导入导出 ⑥

@作者 : SYFStrive @博客首页 : HomePage 📜: PHP MYSQL 📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗 📌:觉得文章不错可以点点关注 👉:

Java中的可变参数

文章目录 可变参数概要可变参数例题可变参数的细节 可变参数概要 看到有道题&#xff0c;让你求出n个数的和&#xff0c;那么这个时候就可以使用Java中的可变参数来做。 格式&#xff1a; 数据类型...参数名 例如&#xff1a; int...args 其实可变参数的本质就是一个数组&…

Linux进程信号【信号处理】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 &#x1f307;前言&#x1f3d9;️正文1、信号的处理时机1.1、处理情况1.2、"合适" 的时机 2、用户态与内…

逆向(异或)

在学习逆向前我们需要掌握一些汇编的基础知识的 同时我们得知道可执行文件的原理 计算机生成可执行文件&#xff0c;我们大致可以简单的这么理解 1.asm源程序文件 2.asm源程序生成obj也就是目标文件 3.由目标文件链接生成可执行文件&#xff0c;Windows的可执行文件通常是EXE&a…

【运维工程师学习三】shell编程

【运维工程师学习三】shell编程 1、系统中sh命令是bash的软链接2、Shell脚本标准格式之文件后缀3、Shell脚本标准格式之文件内容首行4、Shell脚本的运行方法一、作为可执行程序解释 二、作为解释器&#xff08;bash&#xff09;参数 5、find、grep、xargs、sort、uniq、tr、cut…