动态执行流程分析和性能瓶颈分析的利器——gperftools的Cpu Profiler
? ? ? ? 在《動(dòng)態(tài)執(zhí)行流程分析和性能瓶頸分析的利器——valgrind的callgrind》中,我們領(lǐng)略了valgrind對(duì)流程和性能瓶頸分析的強(qiáng)大能力。本文將介紹擁有相似能力的gperftools的Cpu Profiler。(轉(zhuǎn)載請(qǐng)指明出于breaksoftware的csdn博客)
? ? ? ? 我們依然以callgrind一文中的例子為例
#include <thread>
#include <unistd.h>class base {
public:virtual void calc_num() = 0;
public:void add_num () {n++;}
protected:unsigned long long n;
};class inheritA final :public base
{
public:void calc_num() {n = _calc();}
private:unsigned long long _calc() {return 0;}
};class inheritB final :public base
{
public:void calc_num() {n = 0;}
};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;
}
? ? ? ? 這段代碼啟動(dòng)了兩個(gè)線程,分別執(zhí)行inheritA和inheritB的calc_num和add_num方法。其中calc_num被inheritA和inheritB分別實(shí)現(xiàn),add_num則是其基類base實(shí)現(xiàn)的。
? ? ? ? 我們使用如下指令編譯
g++ cpu_profiler.cpp -g -ltcmalloc_and_profiler -lpthread -std=c++11 -o cpu_profiler
? ? ? ? 這次我們主要鏈接了profiler和tcmalloc,官方文檔上說只要鏈接profiler就行,但是經(jīng)過我測(cè)試,必須要同時(shí)鏈接這兩個(gè)庫才可以使用下面的方式去分析
CPUPROFILE=cpu_perf.prof ./cpu_profiler
? ? ? ? 這樣在當(dāng)前目錄下產(chǎn)生了cpu_perf.prof文件。對(duì)于這個(gè)文件,我們還需要使用pprof去分析
pprof --text ./cpu_profiler cpu_perf.prof
? ? ? ? 上面指令指出使用text形式輸出
Using local file ./cpu_profiler.
Using local file cpu_perf.prof.
Total: 1919 samples726 37.8% 37.8% 1919 100.0% thread_routine725 37.8% 75.6% 725 37.8% base::add_num220 11.5% 87.1% 307 16.0% inheritA::calc_num161 8.4% 95.5% 161 8.4% inheritB::calc_num87 4.5% 100.0% 87 4.5% inheritA::_calc0 0.0% 100.0% 1919 100.0% __GI___clone0 0.0% 100.0% 1919 100.0% start_thread0 0.0% 100.0% 1919 100.0% std::__invoke0 0.0% 100.0% 1919 100.0% std::__invoke_impl0 0.0% 100.0% 1919 100.0% std::error_code::default_error_condition0 0.0% 100.0% 1919 100.0% std::thread::_Invoker::_M_invoke0 0.0% 100.0% 1919 100.0% std::thread::_Invoker::operator0 0.0% 100.0% 1919 100.0% std::thread::_State_impl::_M_run
? ? ? ? 需要注意的是,這段不是調(diào)用堆棧,而是各個(gè)操作自身耗時(shí)的排序。
? ? ? ? 為了更直觀的表達(dá)流程,我們可以使用callgrind方式輸出分析結(jié)果
pprof --callgrind ./cpu_profiler cpu_perf.prof > cpu_perf.out
? ? ? ? 然后使用kcachegrind可視化去查看
kcachegrind cpu_perf.out
? ? ? ? 可以發(fā)現(xiàn)gperftools并沒有像valgrind區(qū)分線程,而將所有線程的調(diào)用過程在一個(gè)大過程中體現(xiàn)出來。比如thread_routine的下游分別是inheritA的calc_num和inheritB的calc_num,而在一個(gè)線程中,是不可能同時(shí)調(diào)用到這兩個(gè)過程。
? ? ? ? 圖中數(shù)據(jù)的解決和valgrind產(chǎn)生的結(jié)果是類似的。self列代表自身耗時(shí),我們看到thread_routine和base::add_num這兩個(gè)函數(shù)自身占用的CPU是最高的,這往往是我們程序優(yōu)化關(guān)注的重點(diǎn)。
? ? ? ? 我們看一個(gè)實(shí)際的例子
#include <unistd.h>
#include <stdlib.h>
#include <thread>
#include <iostream>void thread_routine(unsigned long long n) {while (true) {const int array_size = 4 * 1024 * 1024;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;
}
? ? ? ? 這段代碼打印出不停自增的數(shù)字,如果數(shù)字越大,我們可以認(rèn)為性能越高。最終上面的程序打印到42310。
? ? ? ? 使用Cpu Profiler分析它結(jié)果如下圖
? ? ? ? 圖中顯示memset函數(shù)自身耗時(shí)最長,基本占用了整個(gè)程序的CPU資源。我們查看代碼,發(fā)現(xiàn)其8~9行申請(qǐng)了4M的空間,并且將其設(shè)置為空。如何優(yōu)化呢?我們可以將其空間變小,使得memset操作空間減少以提高性能。我們將空間改成32字節(jié)。其執(zhí)行結(jié)果到1640773,是之前方案(42310)38倍。
? ? ? ? 這個(gè)時(shí)候,我們?cè)倏聪缕鋱?zhí)行流程
? ? ? ? 可以發(fā)現(xiàn),其主要耗時(shí)在“往設(shè)備上寫”這個(gè)操作上了。
? ? ? ? 最后提一句,如果不能修改待分析程序的鏈接庫,則可以使用下面指令來分析
LD_PRELOAD="/usr/local/lib/libprofiler.so" CPUPROFILE=cpu_perf.prof ./cpu_profiler
總結(jié)
以上是生活随笔為你收集整理的动态执行流程分析和性能瓶颈分析的利器——gperftools的Cpu Profiler的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 堆状态分析的利器——gperftools
- 下一篇: 互斥量、读写锁长占时分析的利器——val