在Linux下做性能分析3:perf

news2024/10/5 21:17:13

==介绍==

ftrace的跟踪方法是一种总体跟踪法,换句话说,你统计了一个事件到下一个事件所有的时间长度,然后把它们放到时间轴上,你可以知道整个系统运行在时间轴上的分布。

这种方法很准确,但跟踪成本很高。所以,我们也需要一种抽样形态的跟踪方法。perf提供的就是这样的跟踪方法。

perf的原理是这样的:每隔一个固定的时间,就在CPU上(每个核上都有)产生一个中断,在中断上看看,当前是哪个pid,哪个函数,然后给对应的pid和函数加一个统计值,这样,我们就知道CPU有百分几的时间在某个pid,或者某个函数上了。这个原理图示如下:

很明显可以看出,这是一种采样的模式,我们预期,运行时间越多的函数,被时钟中断击中的机会越大,从而推测,那个函数(或者pid等)的CPU占用率就越高。

这种方式可以推广到各种事件,比如上一个博文我们介绍的ftrace的事件,你也可以在这个事件发生的时候上来冒个头,看看击中了谁,然后算出分布,我们就知道谁会引发特别多的那个事件了。

当然,如果某个进程运气特别好,它每次都刚好躲过你发起探测的位置,你的统计结果可能就完全是错的了。这是所有采样统计都有可能遇到的问题了。

还是用我们介绍ftrace时用到的那个sched_switch为例,我们可以用tracepoint作为探测点,每次内核调用这个函数的时候,就上来看看,到底谁引发了这个跟踪点(这个只能用来按pid分类,按函数分类没有用,因为tracepoint的位置是固定的),比如这样:

sudo perf top -e sched:sched_switch -s pid

当然,perf使用更多是CPU的PMU计数器,PMU计数器是大部分CPU都有的功能,它们可以用来统计比如L1 Cache失效的次数,分支预测失败的次数等。PMU可以在这些计数器的计数超过一个特定的值的时候产生一个中断,这个中断,我们可以用和时钟一样的方法,来抽样判断系统中哪个函数发生了最多的Cache失效,分支预测失效等。

下面是一个分支预测失效的跟踪命令和动态结果:

sudo perf top -e branch-misses

我们从这里就可以看到系统中哪些函数制造了最多的分支预测失败,我们可能就需要在那些函数中考虑一下有没有可能塞进去几个likely()/unlikely()这样的宏了。

而且读者应该也注意到了,perf比起ftrace来说,最大的好处是它可以直接跟踪到整个系统的所有程序(而不仅仅是内核),所以perf通常是我们分析的第一步,我们先看到整个系统的outline,然后才会进去看具体的调度,时延等问题。而且perf本身也告诉你调度是否正常了,比如内核调度子系统的函数占用率特别高,我们可能就知道我们需要分析一下调度过程了。

==使用perf==

perf的源代码就是Linux的源代码目录中,因为它在相当程度上和内核是关联的。它会使用Linux内核的头文件。但你编译内核的时候并不会编译它,你必须主动进入tools/perf目录下面,执行make才行。

perf支持很多功能,make的时候它会自动检查这些功能是否存在。比如前面我们用了tracepoint进行事件收集,你就要保证你的系统中有libtracepoint这个库。perf的自由度设计得相当高,很多功能你都可以没有,并不会影响你的基本功能。

由于perf和内核关联,所以理论上,你用哪个内核,就应该使用对应内核的perf,这能保证接口的一致。所以很多类似Ubuntu这样的发行版,你装哪个内核,就要装对应内核的perf命令,而通过的perf命令入其实只是个脚本,根据你当前的perf命令,调用不同perf版本。

但那只是理论上,实践中,其实perf的用户-内核接口相当稳定,很多时候跨版本使用是没有问题的,由于perf的版本还在高速发展中,而且很多发行版的perf版本没有使能很多功能,我在实践中经常直接找最新的内核自己重新编译版本,好像也没有出过什么问题。读者可以有限度参考这个经验。perf也没有很多的路径依赖,你编译完以后连安装都不用,直接用绝对路径调用你编译的版本即可。

==一般跟踪==

前面我们已经看了几个perf工作的例子了。类似git,docker等多功能工具,perf也是使用perf <子命令>这种模式。所有人首先需要学习的是两个最简单的命令:perf list和perf top。

perf list列出perf可以支持的所有事件。例如这样:

旧版本还会列出所有的tracepoint,但那个列表太长了,新版本已经不列这个东西了,读者可以直接到ftrace那边去看就好了。

perf top可以动态收集和更新统计列表,和很多其他perf命令一样。它支持很多参数,但我们关键要记住两个参数:

1. -e 指定跟踪的事件

-e可以指定前面perf list提供的所有事件(包括没有列出的tracepoint),可以用多个-e指定多个事件同时跟踪(但显示的时候会分开显示)

一个-e也可以直接指定多个事件,中间用逗号隔开即可:

sudo perf top -e branch-misses,cycles

(perf list给出的事件是厂家上传上去给Linux社区的,但有些厂家会有自己的事件统计,没有上传出去,这你需要从厂家的用户手册中获得,这种事件,可以直接用编号表示,比如格式是rXXXX,比如在我们的芯片里面,0x13号表示跨芯片内存访问,你就可以用-e r0013来跟踪软件的跨片访问次数)

事件可以指定后缀,比如我想只跟踪发生在用户态时产生的分支预测失败,我可以这样:

sudo perf top -e branch-misses:u,cycles

全部事件都有这个要求,我还可以:

sudo perf top -e ‘{branch-misses,cycles}:u'

看看perf-list的手册,会找到更多的后缀,后缀我也用得比较少,读者对这个有兴趣,可以自己深入挖掘一下,如果有什么好的使用经验,希望也可以告诉我。

2. -s 指定按什么参数来进行分类

-s参数可以不使用,默认会按函数进行分类,但如果你想按pid来分,就需要靠-s来进行分类了。前面我们已经看过这样的例子了。-s也可以指定多个域(用逗号隔开),例如这样:

sudo perf top -e 'cycles' -s comm,pid,dso

perf-top用来理解,体会perf的功能是比较好的,但实践中用得不多,用得比较多的是perf-record和perf-report命令。perf-record用来启动一次跟踪,而perf-report用来输出跟踪结果。

一般的过程是:

sudo perf record -e 'cycles' -- myapplication arg1 arg2
sudo perf report

下面是一个报告的例子:

perf record在当前目录产生一个perf.data文件(如果这个文件已经存在,旧的文件会被改名为perf.data.old),用来记录过程数据。之后运行的perf report命令会输出统计的结果。perf.data只包含原始数据,perf report需要访问本地的符号表,pid和进程的对应关系等信息来生成报告。所以perf.data不能直接拷贝到其他机器上用的。但你可以通过perf-archive命令把所有这些数据打包,这样移到另一个机器上就可以用了。

请注意,perf-archive是指perf-archive这个命令,不是指perf archive这个子命令。这个命令在编译perf源代码的时候会产生的,如果你的发行版不支持,可以自己编译一个。比较可惜的是,perf-archive备份的代码不能跨平台使用(比如你从arm平台上备份的数据,在x86上是分析不了的)。

perf.data保留前一个版本,可以支持perf diff这个命令,这个命令比较两次两次运行的区别。这样你可以用不同参数运行你的程序,看看运行结果有什么不同,用前面这个cs程序为例,我用4线程对比2线程,就有如下结果:

我们这里看到,增加线程后,heavy_cal的占比大幅下降了10.70%,其他的变化不大。

perf record不一定用于跟踪自己启动的进程,通过指定pid,可以直接跟踪固定的一组进程。另外,大家应该也注意到了,上面给出的跟踪都仅仅跟踪发生在特定pid的事件。但很多模型,比如一个webserver,你其实关心的是整个系统的性能,网络上会占掉一部分CPU,WebServer本身占一部分CPU,存储子系统也会占据部分的CPU,网络和存储不一定就属于你的WebServer这个pid。所以,对于全系统调优,我们常常给record命令加上-a参数,这样可以跟踪整个系统的性能。比如,还是前面这个cs程序的跟踪,如果我用-a命令去跟踪,得到的结果就和原来很不一样了:

大家注意一下Command那一列。那里已经不仅仅有cs这个进程了。

perf report是一个菜单接口,可以一直展开到每个函数的代码的,例如我们要展开上面这个heavy_cal()函数的具体计数,我们在上面回车,选择代码分析,我们可以得到:

perf record还有其他参数可以控制,例如可以通过-c指定事件的触发的事件次数等,那个读者们可以自己看手册。

和perf record/report类似的还有一个perf stat命令,这个命令不计算分布,仅仅进行统计,类似这样:

一般情况下,我觉得这个功能用不上。

==堆栈跟踪==

perf的跟踪有一个错觉需要我们注意,假设我们有一个函数abc(),调用另一个函数def(),在perf的统计中,这两者是分开统计的,就是说,执行def的时间,是不计算abc的时间的,图示如下:

这里,abc()被击中5次,def()被击中5次,ghi被击中1次。这会给我们不少错觉,似乎abc的计算压力不大,实际上不是,你要把def和ghi计算在内才行。

但这又带来另一个问题:可能def不仅仅是abc这个函数调用啊,别人也会调用它呢,这种情况,我们怎么知道是谁导致的?

这种情况我们可以启动堆栈跟踪,也就是每次击中的时候,向上回溯一下调用栈,让调用者也会被击中,这样就就更容易看出问题来,这个原理类似这样:

这种情况,abc击中了11次,def击中了6次,而ghi击中了1次。这样我们可以在一定程度上更容易判断瓶颈的位置。-g命令可以实现这样的跟踪,下面是一个例子:

使用堆栈跟踪后,start_thread上升到前面去了,因为正是它调的heavy_cal。

使用堆栈跟踪要注意的是,堆栈跟踪受扫描深度的限制,太深的堆栈可能回溯不过去,这是有可能影响结果的。

另一个问题是,有些我们从源代码看来是函数调用的,其实在汇编一级并不是函数调用。比如inline函数,宏,都不是函数调用。另外,gcc在很多平台中,会自动把很短的函数变成inline函数,这也不产生函数调用。还有一种是,fastcall函数,通过寄存器传递参数,不会产生调用栈,也有可能不产生调用栈,这个通过调用栈回溯是有可能看不到的。

还有一种更奇葩的情况是,部分平台使用简化的堆栈回溯机制,在堆栈中看见一个地址像是代码段的地址,就认为是调用栈,这些情况都会引起堆栈跟踪上的严重错误。使用者应该对系统的ABI非常熟悉,才能很好驾驭堆栈跟踪这个功能的。

==其他功能==

perf是现在Linux中主推的性能分析工具,几乎每次升级都会有重大更新,连什么benchmarking的功能都做进来了,还有用于专项分析perf-mem这样的命令,用来产生脚本的perf script命令,帮助你用不同的脚本语言分析操作结果。这个用户可以自己看手册去,有前面的基础,这些功能都是很好理解的。

不过特别提一下script命令,虽然它的功能看起来只是用来产生分析脚本的,但我们还常常用来导出原始分析数据,读者可以在perf-record后直接用这个命令来导出结果:

sudo perf script

这里列出每个击中点,你爱怎么处理这些击中点的数据,就全凭你的想象力了。

==perf跟踪的缺陷==

前面已经强调过了,perf跟踪是一种采样跟踪,所以我们必须非常小心采样跟踪本身的问题,一旦模型设置不好,整个分析结果可能都是错的。我们要时刻做好这种准备。

我特别提醒的是,你每次看perf report的报告,首先要去注意一下总共收集了多少个点,如果你只有几十个点,你这个报告就可能很不可信了。

另外,我们要清楚,现代CPU基本上已经不用忙等的方式进入等待了,所以,如果CPU在idle(就是没有任务调度,这种情况只要你的CPU占用率不是100%,必然要发生的),击中任务也会停止,所以,在Idle上是没有点的(你看到Idle函数本身的点并非CPU Idle的点,而是准备进入Idle前后花的时间),所以,perf的统计不能用来让你分析CPU占用率的。ftrace和top等工具才能看CPU占用率,perf是不行的。

perf还有一个问题是对中断的要求,perf很多事件都依赖中断,但Linux内核是可以关中断的,关中断以后,你就无法击中关中断的点了,你的中断会被延迟到开中断的时候,所以,在这样的平台上,你会看到很多开中断之后的函数被密集击中。但它们是无辜的。但更糟糕的是,如果在关中断的时候,发生了多个事件,由于中断控制器会合并相同的中断,你就会失去多次事件,让你的统计发生错误。

现代的Intel平台,基本上已经把PMU中断都切换为NMI中断了(不可屏蔽),所以前面这个问题不存在。但在大部分ARM/ARM64平台上,这个问题都没有解决,所以看这种平台的报告,都要特别小心,特别是你看到_raw_spin_unlock()一类的函数击中极高,你就要怀疑一下你的测试结果了(注意,这个结果也是能用的,只是看你怎么用)。

==小结==

这一篇我们介绍了perf的基本用法,perf通常是我们进行性能分析的第一步,但这一步,要用好也不是那么容易的,我们首先应该掌握它的原理,然后基于一个分析模型逐步用perf来验证我们的猜测,我们才有可能真正发现问题。

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

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

相关文章

有道云笔记迁移到自建服务器Joplin

当前有道云笔记各项业务开始逐渐向会员靠拢&#xff0c;如一开始不受限的多端同步现在非会员限制成了两个终端&#xff0c;估计以后会有越来越多的免费内容会逐渐的向会员转移&#xff0c;因此博主开始考虑自建服务器来搞一个云笔记服务端。 因博主已有黑群晖&#xff0c;并且有…

OK3588运行python程序对摄像头数据进行图像处理

OK3588运行python程序读取usb摄像头 硬件连接用工具测试验证摄像头工作正常python 读取usb摄像头并进行图像处理 硬件连接 把usb摄像头插入HOST的usb接口。 在串口终端运行 sudo v4l2-ctl --list-devices 如下图 当前的usb设备是 /dev/video74 用工具测试验证摄像头工作正常…

基于深度学习的高精度80类动物目标检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度80类动物目标检测识别系统可用于日常生活中或野外来检测与定位80类动物目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的80类动物目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YO…

#4. 背包

4030: 背包 题目内容 有 n n n 个物品&#xff0c;每个物品有大小 w i w_i wi​ 和价值 v i v_i vi​&#xff0c;再给定一个整数 k k k 和背包大小 m m m&#xff0c;对每个 1 ≤ i ≤ n − k 1 1 \le i \le n - k 1 1≤i≤n−k1&#xff0c;求出如果只能选择 i i …

Linux中makefile

第一个版本的makefile Makefile的依赖是从上至下的&#xff0c;换句话说就是目标文件是第一句里的目标&#xff0c;如果不满足执行依赖&#xff0c;就会继续向下执行。如果满足了生成目标的依赖&#xff0c;就不会再继续向下执行了。 Make会自动寻找依赖条件所用到的文件&…

uni-app的H5版本下载跨域问题

前端能正常访问图片&#xff0c;但无法下载 因为路径不经过业务代码&#xff0c;所以需要在nginx配置跨域 代码&#xff1a; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, POST, OPTIONS; add_header Access-Control-Allow-H…

【黑客】网络安全靠自学?你不要命了!

1️⃣网安现状 ❗本文面向所有 想要涉足网安领域 或 已经涉足但仍处在迷茫期 的伙伴&#xff0c;如果你月薪达到了3w&#xff0c;那么请你离开。 如果没有&#xff0c;希望你继续看下去&#xff0c;因为你人生的转折点将从这篇文章开始。 ✈️网络安全&#xff0c;一个近几年大…

路径规划算法:基于斑马优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于斑马优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于斑马优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法斑马…

Keil5新建51单片机demo工程模板

51单片机新建工程使用到的软件有keil5&#xff0c;首先安装好keil5软件&#xff0c;安装教程在:【Keil&#xff08;C51&#xff09;介绍、下载、安装、注册与2个补丁】Keil&#xff08;C51&#xff09;介绍、下载、安装与2个补丁_西晋的no1的博客-CSDN博客 一、新建一个空工程 …

【QT】——基础窗口类QMainWindow

目录 1.概念 2.菜单栏——QMenuBar 3.工具栏 添加QAction 工具栏属性设置 5.状态栏——QStatusBar 6 停靠窗口 属性设置 1.概念 QMainWindow 是标准基础窗口中结构最复杂的窗口&#xff0c;QMainWindow继承QWidget&#xff0c;他在QWidget的基础上提供了菜单栏&#…

网络编程 socket

目录 网络编程 套接字&#xff08;socket&#xff09;1. 认识端口号2. TCP协议3. UDP协议4. 网络字节序列5. 常见的套接字6. socket编程接口6.1 socket常见APIsocket函数recvfrom函数sendto函数read函数 从tcp socket中读取接收数据 6.2 sockaddr结构6.3 地址转换函数6.4 udp s…

input输入日期时间,自动格式化组件/工具

input输入日期时间&#xff0c;自动格式化组件/工具 组件/工具使用说明 只能输入日期时间&#xff0c;不能选择日期时间格式 yyyy-MM-dd HH:mm:ss基于VUE框架输入效果见下图 组件/工具解决的痛点 组件库日期时间组件&#xff0c;选日期时间很花时间 例如想快速得到 2021-1…

OJ练习第137题——有序链表转换二叉搜索树

有序链表转换二叉搜索树 力扣链接&#xff1a;109. 有序链表转换二叉搜索树 题目描述 给定一个单链表的头节点 head &#xff0c;其中的元素 按升序排序 &#xff0c;将其转换为高度平衡的二叉搜索树。 本题中&#xff0c;一个高度平衡二叉树是指一个二叉树每个节点 的左右…

[golang gin框架] 40.Gin商城项目-微服务实战之Captcha验证码微服务

本次内容需要 gin框架基础知识, golang微服务基础知识才能更好理解 一.Captcha验证码功能引入 在前面,讲解了微服务的架构等,这里,来讲解前面商城项目的 Captcha验证码 微服务 ,captcha验证码功能在前台,后端 都要用到 ,可以把它 抽离出来 ,做成微服务功能 编辑 编辑 这个验证码…

MySQL 第七天作业 nosql作业

作业一&#xff1a;string list hash结构中&#xff0c;每个至少完成5个命令&#xff0c;包含插入 修改 删除 查询&#xff0c;list 和hash还需要增加遍历的操作命令 1、 string类型数据的命令操作&#xff1a; &#xff08;1&#xff09; 设置键值&#xff1a; set key1 re…

C++基础与深度解析01——函数基本组成+函数传参的三种方法

0.前期准备 请安装Visual Studio 并学习基本的新建项目、新建CPP文件以及运行代码。 1.从Hello World 开始 1.1代码内容 函数功能&#xff1a;打印“Hello World” #include<iostream> int main() { std::cout << "Hello World" << std::endl…

学习babylon.js --- [1] 初次体验和安装

babylon.js是微软推出的Web3D库&#xff0c;本文写作时版本是6.x&#xff0c;官方介绍如下&#xff0c; 看这意思是6.x系列的版本大大提高了性能&#xff0c;并提供了很多新特性。其Github地址是https://github.com/BabylonJS/Babylon.js 本篇文章讲述使用babylon.js的初次体…

申请阿里云服务器并搭建公网可支持数据上传的HTTP服务器

1. 前言 拥有一台自己的云服务器可以做很多事情。阿里云服务器毫无疑问是国内最好的。 阿里云服务器可以用于各种互联网应用的搭建和运行&#xff0c;提供稳定、高性能的服务。 阿里云服务器的用途&#xff0c;包括但不限于以下几个方面&#xff1a; 网站托管&#xff1a;可以将…

常用adb命令整理

一、adb介绍 adb&#xff1a;Android Debug Bridge&#xff0c;Android 调试桥的缩写&#xff0c;adb 是一个 C/S 架构的命令行工具&#xff0c; 主要由 3 部分组成&#xff1a; 运行在 PC 端的 Client : 可以通过它对 Android 应用进行安装、卸载及调试运行在 PC 端的 Serv…