在《内存、性能问题分析的利器——valgrind》一文中我们简单介绍了下valgrind工具集,本文将使用callgrind工具进行动态执行流程分析和性能瓶颈分析。(转载请指明出于breaksoftware的csdn博客)

之前的《利器》系列中,我们介绍了两种静态分析函数调用关系的工具(《静态分析C语言生成函数调用关系的利器——calltree》和《静态分析C语言生成函数调用关系的利器——cflow》)。一般来说,静态分析工具比较适合做代码前期检查,或者辅助阅读理解。但是对于问题排查,或者非常规的函数调用,动态分析功能可能更适合。

我们以虚函数调用为例。base基类包含一个虚函数calc_num(),一个protected型变量n,和一个将n进行自增的public方法add_num()

class base {
public:virtual void calc_num() = 0;
public:void add_num () {n++;}
protected:unsigned long long n;
};

inheritA类继承于base基类。其定义了一个私有函数_calc()为虚函数calc_num()提供计算结果。为了不引入干扰阅读的逻辑,我们简单给的让其返回0。

class inheritA final :public base
{
public:void calc_num() {n = _calc();}
private:unsigned long long _calc() {return 0;}
};

inheritB也继承虚base基类。它只是简单的实现了虚函数calc_num,让其等于0。

现在我们启动两个线程,t1线程运行的是inheritA逻辑,t2线程运行的是inheritB逻辑。(最后我没释放new出来的对象,只是为了让程序稳定运行起来。这种方式不可以作为参考。)

void thread_routine(base* obj_ptr) {while (true) {obj_ptr->calc_num();obj_ptr->add_num();}
}int main() {base* t1_data = new inheritA;std::thread t1(thread_routine, t1_data);t1.detach();base* t2_data = new inheritB;std::thread t2(thread_routine, t2_data);t2.detach();sleep(10);    return 0;
}

我们使用下面指令进行编译

g++ -O0 -g -std=c++11 -lpthread test.cpp -o test

然后使用valgrind进行分析。因为我们的程序是多线程的,所以要开启--separate-threads=yes

valgrind --tool=callgrind --separate-threads=yes ./test

这样在当前目录下,产生如下文件:callgrind.out.12830,callgrind.out.12830-01,callgrind.out.12830-02,callgrind.out.12830-03。没有后缀的文件只是用于标记这次分析属于哪个进程。01是主线程的信息,02、03是主线程启动的两个子线程的信息。

如果我们文本阅读工具打开这些文件,可以发现内容不太容易解读

fl=(23)
fn=(1202)
39 4
+2 6
cfn=(1230) inheritB::calc_num()
calls=1 -7
* 7
* 81149946
cfn=(1230)
calls=13524991 -7
* 94674937
+1 40574976
cfn=(1208) base::add_num()
calls=13524992 -32
* 135249920
-3 13524991

一个强大的工具,肯定不能这么让人去使用,否则会大大增加了其使用成本。kcachegrind就是一款帮助我们分析这些数据的工具。

在kcachegrind中,打开callgrind.out.12830文件。

这个界面主要分为3个区域。线程信息可以显示该进程有多少线程信息被统计。每个线程信息在图上都有一定的显示宽度,其宽度占比和线程在整个进程中CPU占用占比一致。所以我们看到,在例子中,忙碌的两个子线程在图中显示最为明显,而主线程因为几乎一致在执行sleep,所以我们几乎只能看到一根线。

我们点击线程信息区域的t1,再在线程内函数信息区域点击thread_routine,调用关系图区域显示

以同样步骤切换到t2线程,显示

该图没法显示一个函数内的函数调用关系,比如上图中base::add_num和inheritB::calc_num哪个先调用是看不出来的。但是我们可以看到每个函数内部的CPU资源占用——函数框内部的百分比数值,和每个函数的调用次数——线条旁的数字。这些信息也可以在线程内函数信息区域看到。

有了CPU资源占用占比和调用次数等信息,我们就可以分析性能瓶颈了。虽然在valgrind中运行的程序比正常运行的都要慢很多,但是这种慢可以认为是对所有操作都慢,所以我们只要查看某个过程在整体中的占比就可以了。

我们再次简化例子

#include <unistd.h>
#include <stdlib.h>
#include <thread>
#include <iostream>void thread_routine(unsigned long long n) {while (true) {const int array_size = 32;char buf[array_size] = {0};sprintf(buf, "%lu\n", n++);printf(buf);}
}int main() {std::thread t(thread_routine, 0); t.detach();sleep(10);    return 0;
}

这段程序进行简单的累加和打印操作。经过几次运行,平均每次可以打印到150,000。

稍微改动下代码,将array_size改成1024 * 1024 * 8。

         const int array_size = 1024 * 1024 * 8;

再次编译运行后,发现平均每次可以打印到16,500。

可以见得,改动后程序执行效率只有之前的1/10。这种慢已经慢出一个数量级了!

我们使用valgrind进行分析,过程和之前分析调用关系一样。我们只简单的解读下结果

上图我们看到,memset几乎占用的所有的CPU资源。可是我们代码中没有memset啊!

虽然我们代码中没有显示调用memset,但是在使用0初始化数组时,编译器是使用memset实现的。

那么我们不初始化数组(虽然教课书上教我们需要初始化,但是应用场景和实验室场景需要考虑的问题是不太一样的,要灵活应变),代码改成

        const int array_size = 1024 * 1024 * 8;char buf[array_size]; // = {0};sprintf(buf, "%lu\n", n++);

编译运行之,可以发现程序的效率回到140,000左右。

假如我们对这个数据还不满意,继续使用上述方法分析

最耗时的是vfprintf,其占到了82.98%的CPU资源。代码中printf和sprintf都会调用到它,且它们调用次数相等——132,837次,这也和代码逻辑是一致的。但是相同调用次数下,不同渠道来的CPU资源占比不一样。printf(包括自身和其调用的vfprintf)资源占比只有37.85%,而sprintf资源占比则有60.63%。那么如果我们优化掉sprintf,则调用效率应该又会有所提升。

把代码改成

void thread_routine(unsigned long long n) {while (true) {printf("%lu\n", n++);}
}

编译运行后,还是输出还是在150,000!

这并不符合我们的分析,那什么原因呢?

屏幕设备也是一种资源!我们在屏幕上输出信息也是占用一种资源,而且这种资源是稀缺的。所以我们将输出重定向到文件中,则发现优化前的方案可以输出到60,000,000左右;优化后的方案可以输出到80,000,000。虽然效率增幅没有想象中那么大,但是也有33%。

动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind相关推荐

  1. 动态执行流程分析和性能瓶颈分析的利器——gperftools的Cpu Profiler

    在<动态执行流程分析和性能瓶颈分析的利器--valgrind的callgrind>中,我们领略了valgrind对流程和性能瓶颈分析的强大能力.本文将介绍拥有相似能力的gperftools ...

  2. hive执行流程(3)-Driver类分析1Driver类整体流程

    Driver类是对 1 org.apache.hadoop.hive.ql.processors.CommandProcessor.java 接口的实现,重写了run方法,定义了常见sql的执行方式. ...

  3. etrace 跟踪程序函数动态执行流程

    https://github.com/elcritch/etrace 窗口1: 监控窗口,执行监控程序,显示监控结果[root@monitor example]# pwd /root/etrace-m ...

  4. djangorestframework源码分析2:serializer序列化数据的执行流程

    djangorestframework源码分析 本文环境python3.5.2,djangorestframework (3.5.1)系列 djangorestframework源码分析-serial ...

  5. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53939176 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  6. 从源码的角度理解Glide的执行流程

    转自:http://blog.csdn.net/guolin_blog/article/details/53939176 在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架 ...

  7. CGLIB动态代理对象执行流程分析

    前言 都说CGLIB动态代理对象执行方法的速度相较于JDK动态代理更快,那么为什么更快,实际是因为CGLIB中采用了FastClass机制,本篇文章将对CGLIB动态代理对象执行某一个方法的流程进行分 ...

  8. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)

    原文地址:http://www.cnblogs.com/dongying/p/4142476.html 最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍 ...

  9. 【网络安全】Metasploit生成的Shellcode的导入函数解析以及执行流程分析(2)

    密码破解的利器--彩虹表(rainbow table) 确定 shellcode 依赖于哪些导入将使研究人员进一步了解其其余逻辑.不用动态分析shellcode,并且考虑到研究人员已经弄清楚了上面的哈 ...

最新文章

  1. mysql5.7忘记root密码的修改方法
  2. 《应届生求职面试全攻略》学习笔记(三)——面试题目分类讲解
  3. 笔记本电脑键盘切换_全球首款折叠屏笔记本电脑ThinkPad X1 Fold:5G高速互联拥抱PC场景融合时代...
  4. php点击查看更多,微信小程序加载更多和点击查看更多功能介绍
  5. BI-SqlServer
  6. case是java关键字吗_Java关键字
  7. 5G高频段频谱规划启动 大国博弈加剧
  8. iPhone 11办理联通5G套餐后,上网速度变快?网友:发广告翻车了?
  9. SpringMVC工作总结001_SpringMVC拦截器(资源和权限管理)
  10. 如何做好Web接口测试
  11. 深入理解 flex 布局以及计算_Flexbox, Layout
  12. POJ-1149(网络流)
  13. 64位虚拟机Guest OS安装错误:0xC0000225
  14. 配置防盗链 访问控制Directory 访问控制FilesMatch
  15. 基于java的小区物业报修管理系统
  16. 麦克风阵列概述与波束形成—(1) 麦克风阵列概述
  17. windows 文件夹正在使用 “操作无法完成,因为其中的文件夹或文件已在另一程序中打开“ 解决办法
  18. DNT精英论坛(暨.NET北京俱乐部)第2期:.NET依赖注入在区块链项目AElf中的实践
  19. java界面绘制地铁路线_基于 HTML5 Canvas实现 的交互式地铁线路图
  20. 利用二进制位求平均值

热门文章

  1. 机器学习中的数学基础:(1.1)矩阵特征值和特征向量的几何意义
  2. 记录服务器连接jupyter notebook过程
  3. Leetcode 15.三数之和 双指针 or 暴力哈希
  4. POJ - 1386 Play on Words
  5. Matlab人脸检测算法详解
  6. dist包编译html_gulp4 多页面项目管理打包(html, es6,less编译压缩版本控制)
  7. db2 linux 导入数据_「软件资料」-「软件使用」-Linux 导入、导出 MySQL 数据库命令...
  8. OpenCV中 Mat 按行或按列合并程序
  9. 现代计算机三大科学计算,计算机的三大特点是什么?
  10. python怎么编程乘法口诀表_用python编写乘法口诀表的方法