日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

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

發(fā)布時(shí)間:2023/11/27 生活经验 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

? ? ? ? 在《內(nèi)存、性能問題分析的利器——valgrind》一文中我們簡單介紹了下valgrind工具集,本文將使用callgrind工具進(jìn)行動(dòng)態(tài)執(zhí)行流程分析和性能瓶頸分析。(轉(zhuǎn)載請指明出于breaksoftware的csdn博客)

? ? ? ? 之前的《利器》系列中,我們介紹了兩種靜態(tài)分析函數(shù)調(diào)用關(guān)系的工具(《靜態(tài)分析C語言生成函數(shù)調(diào)用關(guān)系的利器——calltree》和《靜態(tài)分析C語言生成函數(shù)調(diào)用關(guān)系的利器——cflow》)。一般來說,靜態(tài)分析工具比較適合做代碼前期檢查,或者輔助閱讀理解。但是對于問題排查,或者非常規(guī)的函數(shù)調(diào)用,動(dòng)態(tài)分析功能可能更適合。

? ? ? ? 我們以虛函數(shù)調(diào)用為例。base基類包含一個(gè)虛函數(shù)calc_num(),一個(gè)protected型變量n,和一個(gè)將n進(jìn)行自增的public方法add_num()

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

? ? ? ??inheritA類繼承于base基類。其定義了一個(gè)私有函數(shù)_calc()為虛函數(shù)calc_num()提供計(jì)算結(jié)果。為了不引入干擾閱讀的邏輯,我們簡單給的讓其返回0。

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

? ? ? ??inheritB也繼承虛base基類。它只是簡單的實(shí)現(xiàn)了虛函數(shù)calc_num,讓其等于0。

? ? ? ? 現(xiàn)在我們啟動(dòng)兩個(gè)線程,t1線程運(yùn)行的是inheritA邏輯,t2線程運(yùn)行的是inheritB邏輯。(最后我沒釋放new出來的對象,只是為了讓程序穩(wěn)定運(yùn)行起來。這種方式不可以作為參考。)

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

? ? ? ? 我們使用下面指令進(jìn)行編譯

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

? ? ? ? 然后使用valgrind進(jìn)行分析。因?yàn)槲覀兊某绦蚴嵌嗑€程的,所以要開啟--separate-threads=yes

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

? ? ? ? 這樣在當(dāng)前目錄下,產(chǎn)生如下文件:callgrind.out.12830,callgrind.out.12830-01,callgrind.out.12830-02,callgrind.out.12830-03。沒有后綴的文件只是用于標(biāo)記這次分析屬于哪個(gè)進(jìn)程。01是主線程的信息,02、03是主線程啟動(dòng)的兩個(gè)子線程的信息。

? ? ? ? 如果我們文本閱讀工具打開這些文件,可以發(fā)現(xiàn)內(nèi)容不太容易解讀

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

? ? ? ? 一個(gè)強(qiáng)大的工具,肯定不能這么讓人去使用,否則會(huì)大大增加了其使用成本。kcachegrind就是一款幫助我們分析這些數(shù)據(jù)的工具。

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

? ? ? ? 這個(gè)界面主要分為3個(gè)區(qū)域。線程信息可以顯示該進(jìn)程有多少線程信息被統(tǒng)計(jì)。每個(gè)線程信息在圖上都有一定的顯示寬度,其寬度占比和線程在整個(gè)進(jìn)程中CPU占用占比一致。所以我們看到,在例子中,忙碌的兩個(gè)子線程在圖中顯示最為明顯,而主線程因?yàn)閹缀跻恢略趫?zhí)行sleep,所以我們幾乎只能看到一根線。

? ? ? ? 我們點(diǎn)擊線程信息區(qū)域的t1,再在線程內(nèi)函數(shù)信息區(qū)域點(diǎn)擊thread_routine,調(diào)用關(guān)系圖區(qū)域顯示

? ? ? ? 以同樣步驟切換到t2線程,顯示

? ? ? ? 該圖沒法顯示一個(gè)函數(shù)內(nèi)的函數(shù)調(diào)用關(guān)系,比如上圖中base::add_num和inheritB::calc_num哪個(gè)先調(diào)用是看不出來的。但是我們可以看到每個(gè)函數(shù)內(nèi)部的CPU資源占用——函數(shù)框內(nèi)部的百分比數(shù)值,和每個(gè)函數(shù)的調(diào)用次數(shù)——線條旁的數(shù)字。這些信息也可以在線程內(nèi)函數(shù)信息區(qū)域看到。

? ? ? ? 有了CPU資源占用占比和調(diào)用次數(shù)等信息,我們就可以分析性能瓶頸了。雖然在valgrind中運(yùn)行的程序比正常運(yùn)行的都要慢很多,但是這種慢可以認(rèn)為是對所有操作都慢,所以我們只要查看某個(gè)過程在整體中的占比就可以了。

? ? ? ? 我們再次簡化例子

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

? ? ? ? 這段程序進(jìn)行簡單的累加和打印操作。經(jīng)過幾次運(yùn)行,平均每次可以打印到150,000。

? ? ? ? 稍微改動(dòng)下代碼,將array_size改成1024 * 1024 * 8。

         const int array_size = 1024 * 1024 * 8;

? ? ? ? 再次編譯運(yùn)行后,發(fā)現(xiàn)平均每次可以打印到16,500。

? ? ? ? 可以見得,改動(dòng)后程序執(zhí)行效率只有之前的1/10。這種慢已經(jīng)慢出一個(gè)數(shù)量級(jí)了!

? ? ? ? 我們使用valgrind進(jìn)行分析,過程和之前分析調(diào)用關(guān)系一樣。我們只簡單的解讀下結(jié)果

? ? ? ? 上圖我們看到,memset幾乎占用的所有的CPU資源。可是我們代碼中沒有memset啊!

? ? ? ? 雖然我們代碼中沒有顯示調(diào)用memset,但是在使用0初始化數(shù)組時(shí),編譯器是使用memset實(shí)現(xiàn)的。

? ? ? ? 那么我們不初始化數(shù)組(雖然教課書上教我們需要初始化,但是應(yīng)用場景和實(shí)驗(yàn)室場景需要考慮的問題是不太一樣的,要靈活應(yīng)變),代碼改成

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

? ? ? ? 編譯運(yùn)行之,可以發(fā)現(xiàn)程序的效率回到140,000左右。

? ? ? ? 假如我們對這個(gè)數(shù)據(jù)還不滿意,繼續(xù)使用上述方法分析

? ? ? ? 最耗時(shí)的是vfprintf,其占到了82.98%的CPU資源。代碼中printf和sprintf都會(huì)調(diào)用到它,且它們調(diào)用次數(shù)相等——132,837次,這也和代碼邏輯是一致的。但是相同調(diào)用次數(shù)下,不同渠道來的CPU資源占比不一樣。printf(包括自身和其調(diào)用的vfprintf)資源占比只有37.85%,而sprintf資源占比則有60.63%。那么如果我們優(yōu)化掉sprintf,則調(diào)用效率應(yīng)該又會(huì)有所提升。

? ? ? ? 把代碼改成

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

? ? ? ? 編譯運(yùn)行后,還是輸出還是在150,000!

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

? ? ? ? 屏幕設(shè)備也是一種資源!我們在屏幕上輸出信息也是占用一種資源,而且這種資源是稀缺的。所以我們將輸出重定向到文件中,則發(fā)現(xiàn)優(yōu)化前的方案可以輸出到60,000,000左右;優(yōu)化后的方案可以輸出到80,000,000。雖然效率增幅沒有想象中那么大,但是也有33%。

總結(jié)

以上是生活随笔為你收集整理的动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。