天天看点

C++ Perf性能调优+火焰图调优系列服务器开发前言一、PERF是什么二、火焰图是什么二、c++代码优化过程总结

系列服务器开发

文章目录

  • 系列服务器开发
  • 前言
  • 一、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

C++ Perf性能调优+火焰图调优系列服务器开发前言一、PERF是什么二、火焰图是什么二、c++代码优化过程总结

发现热点函数是

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;
}
           

再次监控堆栈,分析热点函数,如下,明显已经得到了优化

C++ Perf性能调优+火焰图调优系列服务器开发前言一、PERF是什么二、火焰图是什么二、c++代码优化过程总结

经过优化后,火焰图如下

C++ Perf性能调优+火焰图调优系列服务器开发前言一、PERF是什么二、火焰图是什么二、c++代码优化过程总结

火焰图分析技巧

纵轴代表调用栈的深度(栈桢数),用于表示函数间调用关系:下面的函数是上面函数的父函数。

横轴代表调用频次,一个格子的宽度越大,越说明其可能是瓶颈原因。

不同类型火焰图适合优化的场景不同,比如 on-cpu 火焰图适合分析 cpu 占用高的问题函数,off-cpu 火焰图适合解决阻塞和锁抢占问题。

无意义的事情:

横向先后顺序是为了聚合,跟函数间依赖或调用关系无关;

火焰图各种颜色是为方便区分,本身不具有特殊含义

通过上面火焰图分析:发现_libc_writev、__GI___libc_nanosleep、__GI___libc_close、__GI___libc_close等可能是瓶颈、要结合代码来一并分析

总结

通过本文介绍应该对PERF有了基本的认识,还有很多方法来查看软件的热点函数,如GPERF、GPROF等,你可以自己深入去研究,本文希望能够起到抛砖引玉的作用。

继续阅读