性能優(yōu)化-一個(gè)命令發(fā)現(xiàn)性能問(wèn)題
本文轉(zhuǎn)載自微信公眾號(hào)「編程珠璣」,作者守望先生 。轉(zhuǎn)載本文請(qǐng)聯(lián)系編程珠璣公眾號(hào)。
原文鏈接:https://mp.weixin.qq.com/s/pZtqz2tl4ArbCnzdDdDPwQ
為了取得程序的一丁點(diǎn)性能提升而大幅度增加技術(shù)的復(fù)雜性和晦澀性能,這個(gè)買(mǎi)賣(mài)做不得,這不僅僅是因?yàn)閺?fù)雜的代碼容器滋生bug,也因?yàn)樗麜?huì)使日后的閱讀和維護(hù)工作要更加艱難。
為什么要性能優(yōu)化
也許是想要支持更高的吞吐量,想要更小的延遲,或者提高資源的利用率等,這些都是性能優(yōu)化的目標(biāo)之一。不過(guò)需要提醒的是,不要過(guò)早的進(jìn)行性能優(yōu)化。如果當(dāng)前并沒(méi)有任何性能問(wèn)題,又何必耗費(fèi)這個(gè)精力呢?當(dāng)前一些有助于提高性能的編碼習(xí)慣還是可以時(shí)刻保持的。
目標(biāo)
全面的性能優(yōu)化不是一件簡(jiǎn)單的事情。本系列文章不在于介紹性能優(yōu)化原理或者特定的算法優(yōu)化。旨在分享一些實(shí)踐中常用到的技巧,同時(shí)也主要關(guān)注CPU方面。
如何發(fā)現(xiàn)性能瓶頸
解決性能問(wèn)題的第一步是發(fā)現(xiàn)性能問(wèn)題。如何快速發(fā)現(xiàn)性能問(wèn)題呢?對(duì)于本文來(lái)說(shuō),如何發(fā)現(xiàn)那些使CPU不停地瞎忙的代碼呢?為什么這里是說(shuō)讓CPU瞎忙的代碼?
舉個(gè)例子,完成某個(gè)事情,你可能只需要一個(gè)CPU時(shí)間片,但是由于代碼不夠好,使得仍然需要多個(gè)CPU時(shí)間片。導(dǎo)致CPU非常忙碌,而無(wú)法繼續(xù)提高它的效率。
top
這個(gè)命令相信大家都用過(guò),可以實(shí)時(shí)看到進(jìn)程的一些狀態(tài)。它的使用方法有很多文章不厭其煩地對(duì)其進(jìn)行了介紹,本文不打算進(jìn)行介紹。我們可以通過(guò)top命令看到某個(gè)進(jìn)程占用的CPU,但是CPU占用高并不代表它有性能問(wèn)題,也有可能是CPU正在有效地高速運(yùn)轉(zhuǎn),并沒(méi)有占著茅坑不拉屎。
快速發(fā)現(xiàn)
想必我們都聽(tīng)過(guò)八二法則,同樣的,80%的性能問(wèn)題集中于20%的代碼。因此我們只要找到這20%的部分代碼,就可以有效地解決一些性能問(wèn)題。
本文使用perf命令,它很強(qiáng)大,支持的參數(shù)也非常多,不過(guò)沒(méi)關(guān)系,本文也沒(méi)打算全部介紹。
系統(tǒng)中可能沒(méi)有perf命令,ubuntu可以使用如下方法安裝:
- sudo apt install linux-tools-common
實(shí)例
直接來(lái)看示例吧。例子很簡(jiǎn)單,只是將字符串的字母轉(zhuǎn)為大寫(xiě)罷了。當(dāng)然了,很多人可能一眼就看出了哪里有性能問(wèn)題,不過(guò)沒(méi)關(guān)系,這個(gè)例子只是為了說(shuō)明perf的應(yīng)用。
- //來(lái)源:公眾號(hào)【編程珠璣】
- //作者:守望先生
- //toUpper.c
- #include<stdlib.h>
- #include<stdio.h>
- #include<time.h>
- #include<ctype.h>
- #include<string.h>
- #include<sys/time.h>
- #define MAX_LEN 1024*1024
- void printCostTime(struct timeval *start,struct timeval *end)
- {
- if(NULL == start || NULL == end)
- {
- return;
- }
- long cost = (end->tv_sec - start->tv_sec) * 1000 + (end->tv_usec - start->tv_usec)/1000;
- printf("cost time: %ld ms\n",cost);
- }
- int main(void)
- {
- srand(time(NULL));
- int min = 'a';
- int max = 'z';
- char *str = malloc(MAX_LEN);
- //申請(qǐng)失敗則退出
- if(NULL == str)
- {
- printf("failed\n");
- return -1;
- }
- unsigned int i = 0;
- while(i < MAX_LEN)//生成隨機(jī)數(shù)
- {
- str[i] = ( rand() % ( max - min ) ) + min;
- i++;
- }
- str[MAX_LEN - 1] = 0;
- struct timeval start,end;
- gettimeofday(&start,NULL);
- for(i = 0;i < strlen(str) ;i++)
- {
- str[i] = toupper( str[i] );
- }
- gettimeofday(&end,NULL);
- printCostTime(&start,&end);
- free(str);
- str = NULL;
- return 0;
- }
編譯成可執(zhí)行程序并運(yùn)行:
- $ gcc -o toUpper toUpper.c
- $ ./toUpper
這個(gè)時(shí)候我們用top查看結(jié)果發(fā)現(xiàn)toUpper程序占用CPU 100%:
- $ top -p `pidof toUpper`
- PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
- 24456 root 20 0 5248 2044 952 R 100.0 0.0 0:07.13 toUpper
打開(kāi)另外一個(gè)終端,執(zhí)行命令:
- $ perf top -p `pidof toUpper`
- Samples: 1K of event 'cycles:ppp', Event count (approx.): 657599945
- Overhead Shared Object Symbol
- 99.13% libc-2.23.so [.] strlen
- 0.19% [kernel] [k] perf_event_task_tick
- 0.11% [kernel] [k] prepare_exit_to_usermode
- 0.10% libc-2.23.so [.] toupper
- 0.09% [kernel] [k] rcu_check_callbacks
- 0.09% [kernel] [k] reweight_entity
- 0.09% [kernel] [k] task_tick_fair
- 0.09% [kernel] [k] native_write_msr
- 0.09% [kernel] [k] trigger_load_balance
- 0.00% [kernel] [k] native_apic_mem_write
- 0.00% [kernel] [k] __perf_event_enable
- 0.00% [kernel] [k] intel_bts_enable_local
其中pidof命令用于獲取指定程序名的進(jìn)程ID。
看到結(jié)果了嗎?可以很清楚地看到,strlen函數(shù)占用了整個(gè)程序99%的CPU,那這個(gè)CPU的占用是否可以優(yōu)化掉呢?我們現(xiàn)在都清楚,顯然是可以的,在對(duì)每一個(gè)字符串進(jìn)行大寫(xiě)轉(zhuǎn)換時(shí),都進(jìn)行了字符串長(zhǎng)度的計(jì)算,顯然是沒(méi)有必要,可以拿到循環(huán)之外的。
同時(shí)我們也關(guān)注到,這里面有很多符號(hào)可能完全沒(méi)見(jiàn)過(guò),不知道什么含義了,比例如reweight_entity,不過(guò)我們知道它前面有著kernel字樣,因此也就明白,這是內(nèi)核干的事情,僅此而已。
這里實(shí)時(shí)查看的方法,當(dāng)然你也可以保存信息進(jìn)行查看。
- $ perf record -e cycles -p `pidof toUpper` -g -a
執(zhí)行上面的命令一段時(shí)間,用于采集相關(guān)性能和符號(hào)信息,隨后ctrl+c中止。默認(rèn)當(dāng)前目錄下生成perf.data,不過(guò)這里面的數(shù)據(jù)不易閱讀,因此執(zhí)行:
- $ perf report
- + 100.00% 0.00% toUpper [unknown] [k] 0x03ee258d4c544155
- + 100.00% 0.00% toUpper libc-2.23.so [.] __libc_start_main
- + 99.72% 99.34% toUpper libc-2.23.so [.] strlen
- 0.21% 0.02% toUpper [kernel.kallsyms] [k] apic_timer_interrupt
- 0.19% 0.00% toUpper [kernel.kallsyms] [k] smp_apic_timer_interrupt
- 0.16% 0.00% toUpper [kernel.kallsyms] [k] ret_from_intr
- 0.16% 0.00% toUpper [kernel.kallsyms] [k] hrtimer_interrupt
- 0.16% 0.00% toUpper [kernel.kallsyms] [k] do_IRQ
- 0.15% 0.15% toUpper libc-2.23.so [.] toupper
- 0.15% 0.00% toUpper [kernel.kallsyms] [k] handle_irq
- 0.15% 0.00% toUpper [kernel.kallsyms] [k] handle_edge_irq
- 0.15% 0.00% toUpper [kernel.kallsyms] [k] handle_irq_event
- 0.15% 0.00% toUpper [kernel.kallsyms] [k] handle_irq_event_percpu
- 0.14% 0.00% toUpper [kernel.kallsyms] [k] __handle_irq_event_percpu
- 0.14% 0.01% toUpper [kernel.kallsyms] [k] __hrtimer_run_queues
- 0.13% 0.00% toUpper [kernel.kallsyms] [k] _rtl_pci_interrupt
其中-g參數(shù)為了保存調(diào)用調(diào)用鏈,-a表示保存所有CPU信息。
因此就可以看到采樣信息了,怎么樣是不是很明顯,其中的+部分還可以展開(kāi),看到調(diào)用鏈。
例如展開(kāi)的部分信息如下:
- - 100.00% 0.00% toUpper libc-2.23.so [.] __libc_start_main
- - __libc_start_main
- 99.72% strlen
當(dāng)然了,實(shí)際上你也可以將結(jié)果重定向到另外一個(gè)文件,便于查看:
- $ perf report > result
- $ more result
- # Event count (approx.): 23881569776
- #
- # Children Self Command Shared Object Symbol
- # ........ ........ ....... ................. ..............................
- ...................
- #
- 100.00% 0.00% toUpper [unknown] [k] 0x03ee258d4c544155
- |
- ---0x3ee258d4c544155
- __libc_start_main
- |
- --99.72%--strlen
- 100.00% 0.00% toUpper libc-2.23.so [.] __libc_start_main
- |
- ---__libc_start_main
- |
- --99.72%--strlen
- 99.72% 99.34% toUpper libc-2.23.so [.] strlen
- |
- --99.34%--0x3ee258d4c544155
這樣看也是非常清晰的。
不過(guò)不要高興地太早,并不是所有情況都能清晰的看到具體問(wèn)題在哪里的。
至于本文例子的性能問(wèn)題怎么解決,相信你已經(jīng)很清楚了,只需要把strlen提到循環(huán)外即可,這里不再贅述。
總結(jié)
本文的例子過(guò)于簡(jiǎn)單粗暴,但是足夠說(shuō)明perf的使用,快速發(fā)現(xiàn)程序中占用CPU較高的部分,至于該部分能否被優(yōu)化,是否正常就需要進(jìn)一步分析了。不過(guò)別急,后續(xù)將會(huì)分享一些常見(jiàn)的可優(yōu)化的性能點(diǎn)。
作者:守望,linux應(yīng)用開(kāi)發(fā)者,目前在公眾號(hào)【編程珠璣】 分享Linux/C/C++/數(shù)據(jù)結(jié)構(gòu)與算法/工具等原創(chuàng)技術(shù)文章和學(xué)習(xí)資源。