系列服务器开发
文章目录
- 系列服务器开发
- 前言
- 一、PERF是什么
- 二、火焰图是什么
- 二、c++代码优化过程
- 总结
前言
系统级性能优化通常包括两个阶段:性能剖析(performance profiling)和代码优化。性能剖析的目标是寻找性能瓶颈,查找引发性能问题的原因及热点代码。代码优化的目标是针对具体性能问题而优化代码或编译选项,以改善软件性能。
一、PERF是什么
Linux性能计数器是一个基于内核的子系统,它提供一个性能分析框架,比如硬件(CPU、PMU(Performance Monitoring Unit))功能和软件(软件计数器、tracepoint)功能。通过perf,应用程序可以利用PMU、tracepoint和内核中的计数器来进行性能统计。
PERF是用来进行软件性能分析的工具。通过它,应用程序可以利用 PMU,tracepoint 和内核中的特殊计数器来进行性能统计。它不但可以分析指定应用程序的性能问题 (per thread),也可以用来分析内核的性能问题,当然也可以同时分析应用代码和内核,从而全面理解应用程序中的性能瓶颈。
查看APP热点函数:
安装perf
sudo yum install perf
查看热点函数
ps aux|grep vtest
perf top -p PID
记录一定时间的数据:
perf record -p PID
perf report #需要按CTRL+C取消record,然后执行下面的
记录堆栈,显示调用图:
perf record -a --call-graph fp -p PID
perf report --call-graph --stdio
也可以打印到文件perf report --call-graph --stdio >t.txt。
二、火焰图是什么
火焰图(Flame Graph)是由Linux性能优化大师Brendan Gregg发明的,和所有其他的trace和profiling方法不同的是,Flame Graph以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能的调用栈。其他的呈现方法,一般只能列出单一的调用栈或者非层次化的时间分布。
火焰图类型
常见的火焰图类型有 On-CPU,Off-CPU,还有 Memory,Hot/Cold,Differential 。
火焰图类型 | 横轴含义 | 纵轴含义 | 解决问题 | 采样方式 |
---|---|---|---|---|
On-CPU | CPU占用时间 | 调用栈 | 找出CPU占用高的问题函数,分析代码热路径 | 固定频率采样CPU调用栈 |
生成火焰图:
下载flame
git clone https://github.com/brendangregg/FlameGraph.git
1、首先使用 perf record 命令记录进程的 CPU 使用情况
命令:sudo perf record -e cpu-clock -g -p 16808 或者./t1
2、 使用 perf script 工具对 perf.data 进行解析
命令:sudo perf script -i perf.data &> perf.unfold
3、使用 Flame Graph 工具将 perf.unfold 中的符号折叠 //生成脚本文件
命令:sudo ./stackcollapse-perf.pl perf.unfold &> perf.folded
4、使用 Flame Graph 工具将 perf.folded 生成 svg 火焰图
命令:sudo ./flamegraph.pl perf.folded > perf.svg
火焰图整个图形看起来就像一个跳动的火焰,这就是它名字的由来:
每一列代表一个调用栈,每一个格子代表一个函数
纵轴展示了栈的深度,按照调用关系从下到上排列,最顶上格子代表采样时,正在占用 cpu 的函数。
横轴的意义是指:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。
横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。
火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。
其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。
采样可以是单线程、多线程、多进程甚至是多 host。
二、c++代码优化过程
编译:
g++ test.cpp -o mytest -std=c++11 -fno-elide-constructors
#include <iostream>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include<fstream>
int funC(char* pData,int size)
{
const char *recordpath = "/opt/eDisk/davit.test";
std::ofstream outfile;
outfile.open(recordpath, std::ios_base::out|std::ios_base::app);
outfile.write((const char*) pData, size);
return 0;
}
int funB()
{
int size = 1024*1024*2;
char *pAddr = new char[size];
memset(pAddr,0,size);
funC(pAddr,size);
if(pAddr){
delete [] pAddr;
pAddr = nullptr;
}
return 0;
}
int funA()
{
sleep(2);
funB();
return 0;
}
int main(int argc, char* argv[])
{
while(true){
funA();
}
return 0;
}
ps aux|grep mytest
perf top -p PID
发现热点函数是
1、__memset_sse2
2、_IO_file_fopen
通过上面堆栈、将指针的分配改为全局变量、只分配一次、释放一次,经过优化,代码如下:
#include <iostream>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include<fstream>
char *pAddr;
int size = 1024*1024*2;
int funC(char* pData,int size)
{
const char *recordpath = "/opt/eDisk/davit.test";
std::ofstream outfile;
outfile.open(recordpath, std::ios_base::out|std::ios_base::app);
outfile.write((const char*) pData, size);
return 0;
}
int funB()
{
funC(pAddr,size);
return 0;
}
int funA()
{
sleep(2);
funB();
return 0;
}
int main(int argc, char* argv[])
{
pAddr = new char[size];
memset(pAddr,0,size);
while(true){
funA();
}
if(pAddr){
delete [] pAddr;
pAddr= nullptr;
}
return 0;
}
再次监控堆栈,分析热点函数,如下,明显已经得到了优化
经过优化后,火焰图如下
火焰图分析技巧
纵轴代表调用栈的深度(栈桢数),用于表示函数间调用关系:下面的函数是上面函数的父函数。
横轴代表调用频次,一个格子的宽度越大,越说明其可能是瓶颈原因。
不同类型火焰图适合优化的场景不同,比如 on-cpu 火焰图适合分析 cpu 占用高的问题函数,off-cpu 火焰图适合解决阻塞和锁抢占问题。
无意义的事情:
横向先后顺序是为了聚合,跟函数间依赖或调用关系无关;
火焰图各种颜色是为方便区分,本身不具有特殊含义
通过上面火焰图分析:发现_libc_writev、__GI___libc_nanosleep、__GI___libc_close、__GI___libc_close等可能是瓶颈、要结合代码来一并分析
总结
通过本文介绍应该对PERF有了基本的认识,还有很多方法来查看软件的热点函数,如GPERF、GPROF等,你可以自己深入去研究,本文希望能够起到抛砖引玉的作用。