动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind
在《内存、性能问题分析的利器——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%。
相关文章:

CentOS6.3编译安装Nginx1.4.7 + MySQL5.5.25a + PHP5.3.28
2019独角兽企业重金招聘Python工程师标准>>> 【准备工作】 01 #在编译安装lnmp之前,首先先卸载已存在的rpm包。 02 rpm -e httpd 03 rpm -e mysql 04 rpm -e php 05 06 yum -y remove httpd 07 yum -y remove mysql-server mysql 08 yum -y remove php 0…
GitHub标星14000+,阿里开源的SEATA如何应用到极致?
作者简介:袁鸣凯,家乐福技术总监, 高知特有限技术公司中国区架构师,HP上海研发技术专家,夸客金融首席架构师,现任家乐福中国区技术总监。多年互联网、企业级SOA、微服务、全渠道中台方面的架构设计实战经验…

C++拾趣——有趣的操作符重载
操作符重载是C语言中一个非常有用的特性。它可以让我们比较优雅的简化代码,从而更加方便的编写逻辑。 为什么要使用操作符重载一种常见的用法是重载<<运算符,让标准输出可以输出自定义的类型。比如 #include <iostream>class Sample {friend…

urlparse模块(专门用来解析URL格式)
# -*- coding: utf-8 -*- #python 27 #xiaodeng #urlparse模块(专门用来解析URL格式)#URL格式: #protocol ://hostname[:port] / path / [;parameters][?query]#fragment #parameters:特殊参数,一般用的很少。#1、url…
使用Boost的Serialization库序列化STL标准容器
使用Boost做对象序列化是非常方便的,本文将介绍一种序列化STL标准容器的方法。这是之前设计的异步框架的一个子功能:过程A将标准容器数据序列化成二进制流,然后将该二进制数据发送到过程B,过程B将数据反序列化为标准容器。&#x…
连登GitHub TOP榜,中国开发者在行动!
作者 | 唐小引数据 | 于瑞洋出品 | AI科技大本营(ID:rgznai100)中国开发者正在走向世界中文开源项目正在不断登上 GitHub TOP 榜不久前,一个名叫「wuhan2020」的开源项目进入了 GitHub Trending TOP 榜,截至到现在,已经…

Merge into的使用
用途 merge 命令可以用来用一个表中的数据来修改或者插入到另一个表。插入或者修改的操作取决于on子句的条件。该语句可以在同一语句中执行两步操作,可以减少执行多条insert 和update语句。merge是一个确定性的语句,即不会在同一条merge语句中去对同一条…

PHP和MySQL Web开发从新手到高手,第8天-创建categories管理页面
1. 创建categories管理页面 主要包含以下几个页面: A. index.php, 准备各种变量数据.供展示页面使用. B. categories.html.php, 显示categories. C. form.html.php, 用于编缉或添加作者的页面. 页面郊果: 2. categories页面的主要流程 2.1 是否已登录 if (!user_is_login()){in…

堆状态分析的利器——valgrind的DHAT
在《堆问题分析的利器——valgrind的massif》一文中,我们介绍了如何使用massif查看和分析堆分配/释放的问题。但是除了申请和释放,堆空间还有其他问题,比如堆空间的使用率、使用周期等。通过分析这些问题,我们可以对程序代码进行优…

cisco2950交换机密码恢复
在实际工作中可能会忘记交换机密码,导致不能对交换机进行配置的情况。cisco提供了密码恢复的方法。以下是关于交换机密码恢复说明:如果忘记密码,这时我们如果要配置交换机就必须在启动时绕过config.text的配置【密码保存在config.text中】然后…
程序员SQL都不会?能干啥?资深研发:别再瞎努力了!
国外有人曾做过调查显示:“SQL的使用人数仅次于JavaScript”。更有统计,世界上一流的互联网公司中,排名前 20 的有 80% 都是 MySQL 的忠实用户。为什么这项技术仍有这么多人在用?又为什么值得我们学习?1、无论你是前端…

OC管理文件方法
1、常见的NSFileManager文件方法 -(NSData *)contentsAtPath:path //从一个文件读取数据 -(BOOL)createFileAtPath: path contents:(NSData *)data attributes:attr //向一个文件写入数据 -(BOOL)removeItemAtPath:path error:err //删除一个文件 -(BOOL)moveItemAtPa…
堆状态分析的利器——gperftools的Heap Profiler
在《内存泄漏分析的利器——gperftools的Heap Checker》一文中,我们介绍了如何使用gperftools分析内存泄漏。本文将介绍其另一个强大的工具——Heap Profiler去分析堆的变化过程。(转载请指明出于breaksoftware的csdn博客) 我们使用类似于《堆…

亲戚称呼关系表
直系血亲父系曾曾祖父--曾祖父--祖父--父亲曾曾祖母--曾祖母--祖母--父亲母系曾曾外祖父--曾外祖父--外祖父--母亲曾曾外祖母--曾外祖母--外祖母--母亲儿子:夫妻间男性的第一子代。女儿:夫妻间女性的第一子代。孙:夫妻间的第二子代࿰…
技术驰援抗疫一线, Python 线上峰会免费学!
截至截止2月9号24时,新型冠状病毒在全国已确诊42714例,疑似病例已达21675例。而专家所说的“拐点”始终未至,受疫的影响,各大公司开启远程办公模式,将返回工作场所办公的时间一延再延。在抗疫前线,中国医疗…

ZeroMq实现跨线程通信
ZeroMq实现跨线程通信 之前在技术崇拜的技术经理指导下阅读了ZeroMq的基础代码,现在就将阅读的心得与成果记录一下,并重新模仿实现了一下经理的异步队列。 1、对外接口 //主要接口(1)void *ymq_attach (void *ctx_, int oid, voi…
动态执行流程分析和性能瓶颈分析的利器——gperftools的Cpu Profiler
在《动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind》中,我们领略了valgrind对流程和性能瓶颈分析的强大能力。本文将介绍拥有相似能力的gperftools的Cpu Profiler。(转载请指明出于breaksoftware的csdn博客) 我们依然以callg…

C语言内存管理内幕(二)----半自动内存管理策略
2019独角兽企业重金招聘Python工程师标准>>> C语言内存管理内幕(二)----半自动内存管理策略 转载于:https://my.oschina.net/hengcai001/blog/466
无人机巡逻喊话、疫情排查、送药消毒,抗疫战中机器人化身钢铁战士!
整理 | 夕颜责编 | Carol出品 | CSDN(ID:CSDNnews)这场抗疫战争似乎格外漫长,但回头细数一下才发现,自疫情爆发以来,也不过半月之久。在接下来的几个半月中,抗疫战仍将继续,各方力量也要绷紧神经…

jQuery EasyUI 表单插件 - Datebox 日期框
为什么80%的码农都做不了架构师?>>> 扩展自 $.fn.combo.defaults。通过 $.fn.datebox.defaults 重写默认的 defaults。 日期框(datebox)把可编辑的文本框和下拉日历面板结合起来,用户可以从下拉日历面板中选择日期。在…

互斥量、读写锁长占时分析的利器——valgrind的DRD
在进行多线程编程时,我们可能会存在同时操作(读、写)同一份内存的可能性。为了保证数据的正确性,我们往往会使用互斥量、读写锁等同步方法。(转载请指明出于breaksoftware的csdn博客) 互斥量的用法如下 pth…

一次性同步修改多台linux服务器的密码
如何一次性修改多台linux服务器的密码,这是个问题,我给大家提供一个脚本,是前一段我刚刚写的,希望能对大家有所帮助一 , 需求:linux环境下运行,需要tcl和expect支持原理说明:利用expect的摸拟交互的功能&…
麻省理工学院的新系统TextFooler, 可以欺骗Google的自然语言处理系统及Google Home的音频...
来源 | news.mit编译 | 武明利责编 | Carol出品 | AI科技大本营(ID:rgznai100)两年前,Google的AI还不太成熟。一段时间以来,有一部分计算机科学研究一直致力于更好地理解机器学习模型如何处理这些“对抗性”攻击,这些攻…

Oracle VS DB2 数据类型
Oracle VS DB2 本文转自:http://www.bitscn.com/oracle/install/200604/16541.html首先,通过下表介绍ORACLE与DB2/400数据类型之间的对应关系,是一对多的关系,具体采用哪种对应关系,应具体问题具体分析。 OracleDB2/40…

死锁问题分析的利器——valgrind的DRD和Helgrind
在《DllMain中不当操作导致死锁问题的分析--死锁介绍》一文中,我们介绍了死锁产生的原因。一般来说,如果我们对线程同步技术掌握不牢,或者同步方案混乱,极容易导致死锁。本文我们将介绍如何使用valgrind排查死锁问题。(…
疫情可视化,基于知识图谱的AI“战疫”平台如何做?
来源 | DataExa渊亭科技武汉封城半个月,疫情依然严峻。但与17年前的SARS相比,我国在此次疫情防控工作中展现出了更高的医疗救治水平、更快的防疫反应速度、更透明的信息披露机制、更迅速的数据报送机制。在这场没有硝烟的战役中,社会各界团结…

mysql乐观锁总结和实践
2019独角兽企业重金招聘Python工程师标准>>> 上一篇文章《MySQL悲观锁总结和实践》谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的锁机制实现,以保证操…

数据竞争(data race)问题分析的利器——valgrind的Helgrind
数据竞争(data race)是指在非线程安全的情况下,多线程对同一个地址空间进行写操作。一般来说,我们都会通过线程同步方法来保证数据的安全,比如采用互斥量或者读写锁。但是由于某些笔误或者设计的缺陷,还是存…

sql charindex函数
CHARINDEX函数返回字符或者字符串在另一个字符串中的起始位置。CHARINDEX函数调用方法如下: CHARINDEX ( expression1 , expression2 [ , start_location ] ) Expression1是要到expression2中寻找的字符中,start_location是CHARINDEX函数开始在expressi…
170亿参数加持,微软发布史上最大Transformer模型
来源 | 微软译者 | 刘畅出品 | AI科技大本营(ID:rgznai100)Turing Natural Language Generation(T-NLG)是微软提供的一个有170亿参数的语言模型,在许多NLP任务上均优于目前的SOTA技术。我们向学者演示了该模型…