使用oprofile分析性能瓶頸(1)
1. 概述
oprofile 是 Linux 平台上,類似 INTEL VTune 的一個功能強大的性能分析工具。
其支持兩種採樣(sampling)方式:基於事件的採樣(event based)和基於時間的採樣(time based)。
基於事件的採樣是oprofile只記錄特定事件(比如L2 cache miss)的發生次數,當達到用戶設定的定值時oprofile 就記錄一下(採一個樣)。這種方式需要CPU 內部有性能計數器(performace counter)。現代CPU內部一般都有性能計數器,龍芯2E內部亦內置了2個性能計數器。
基於時間的採樣是oprofile 借助OS 時鐘中斷的機制,每個時鐘中斷 oprofile 都會記錄一次採一次樣)。引入的目的在於,提供對沒有性能計數器 CPU 的支持。其精度相對於基於事件的採樣要低。因為要借助 OS時鐘中斷的支持,對禁用中斷的代碼oprofile不能對其進行分析。
oprofile 在Linux 上分兩部分,一個是內核模塊(oprofile.ko),一個為用戶空間的守護進程(oprofiled)。前者負責訪問性能計數器或者注冊基於時間採樣的函數(使用register_timer_hook注冊之,使時鐘中斷處理程序最后執行profile_tick 時可以訪問之),並採樣置於內核的緩沖區內。后者在后台運行,負責從內核空間收集數據,寫入文件。
2. oprofile 的安裝
以龍芯2E平台為例,要使用oprofile 首先得採用打開oprofile支持的內核啟動。然后安裝下面3個軟件包:oprofile, oprofile-common, oprofile-gui,其中核心軟件包是oprofile-common,其包括以下工具集:
/usr/bin/oprofiled 守護進程
/usr/bin/opcontrol 控制前端,負責控制與用戶交互,用得最多
/usr/bin/opannotate 根據搜集到的數據,在源碼或者匯編層面上注釋並呈現給用戶
/usr/bin/opreport 生成二進制鏡像或符號的概覽
/usr/bin/ophelp 列出oprofile支持的事件
/usr/bin/opgprof 生成gprof格式的剖析數據
...
目前oprofile 在龍芯2E上已經移植好了,包括用戶空間的工具集軟件包,亦可用矣。
3. oprofile 快速上手
a. 初始化
opcontrol --init
該命令會加載oprofile.ko模塊,mount oprofilefs。成功后會在/dev/oprofile/目錄下導出一些文件和目錄如: cpu_type, dump, enable, pointer_size, stats/
b. 配置
主要設置計數事件和樣本計數,以及計數的CPU模式(用戶態、核心態)
opcontrol --setup --event=CYCLES:1000::0:1
則是設置計數事件為CYCLES,即對處理器時鐘周期進行計數
樣本計數為1000,即每1000個時鐘周期,oprofile 取樣一次。
處理器運行於核心態則不計數
運行於用戶態則計數
--event=name:count:unitmask:kernel:user
name: event name, e.g. CYCLES or ICACHE_MISSES
count: reset counter value e.g. 100000
unitmask: hardware unit mask e.g. 0x0f
kernel: whether to profile kernel: 0 or 1
user: whether to profile userspace: 0 or 1
c. 啟動
opcontrol --start
d. 運行待分析之程序
./ffmpeg -c cif -vcodec mpeg4 -i /root/paris.yuv paris.avi
e. 取出數據
opcontrol --dump
opcontrol --stop
f. 分析結果
opreport -l ./ffmpeg
則會輸出如下結果:
CPU: GODSON2E, speed 0 MHz (estimated)
Counted CYCLES events (Cycles) with a unit mask of 0x00 (No unit mask) count 10000
samples % symbol name
11739 27.0148 pix_abs16_c
6052 13.9274 pix_abs16_xy2_c
4439 10.2154 ff_jpeg_fdct_islow
2574 5.9235 pix_abs16_y2_c
2555 5.8798 dct_quantize_c
2514 5.7854 pix_abs8_c
2358 5.4264 pix_abs16_x2_c
1388 3.1942 diff_pixels_c
964 2.2184 ff_estimate_p_frame_motion
852 1.9607 simple_idct_add
768 1.7674 sse16_c
751 1.7283 ff_epzs_motion_search
735 1.6914 pix_norm1_c
619 1.4245 pix_sum_c
561 1.2910 mpeg4_encode_blocks
558 1.2841 encode_thread
269 0.6190 put_no_rnd_pixels16_c
255 0.5868 dct_unquantize_h263_inter_c
......
4. 例子
oprofile 可以分析處理器周期、TLB 失誤、分支預測失誤、緩存失誤、中斷處理程序,等等。你可以使用 opcontrol --list-events 列出當前處理器上可監視事件列表。
下面分析一個編寫不當的例子:
[帶有cache問題的代碼cache.c]
+++++++++++++++++++++++++++++++++++++++++++++++
int matrix[2047][7];
void bad_access()
{
int k, j, sum = 0;
for(k = 0; k < 7; k++)
for(j = 0; j < 2047; j++)
sum += matrix[j][k] * 1024;
}
int main()
{
int i;
for(i = 0; i< 100000; i++)
bad_access();
return 0;
}
+++++++++++++++++++++++++++++++++++++++++++++++
編譯之: gcc -g cache.c -o cache
使用oprofile 分析之:
opcontrol --init
opcontrol --setup --event=DCACHE_MISSES:500::0:1
opcontrol --start && ./cache && opcontrol --dump && opcontrol --stop
使用 opannotate 分析結果為:
/*
* Command line: opannotate --source ./cachee
*
* Interpretation of command line:
* Output annotated source file with samples
* Output all files
*
* CPU: GODSON2E, speed 0 MHz (estimated)
* Counted ICACHE_MISSES events (Instruction Cache misses number ) with a unit mask of 0x00 (No unit mask) count 500
*/
/*
* Total samples for file : "/comcat/test/pmc.test/cachee.c"
*
* 34 100.000
*/
:int matrix[2047][7];
:
:void bad_access()
:{ /* bad_access total: 33 97.0588 */
: int k, j, sum = 0;
:
: for(k = 0; k < 7; k++)
33 97.0588 : for(j = 0; j < 2047; j++)
: sum += matrix[j][k] * 1024;
:
:}
:
:int main()
:{ /* main total: 1 2.9412 */
: int i;
:
1 2.9412 : for(i = 0; i< 10000; i++)
: bad_access();
:
: return 0;
:
:}
:
opreport 解析的結果為:
GodSonSmall:/comcat/test/pmc.test# opreport -l ./cache
CPU: GODSON2E, speed 0 MHz (estimated)
Counted ICACHE_MISSES events (Instruction Cache misses number ) with a unit mask of 0x00 (No unit mask) count 500
samples % symbol name
33 97.0588 bad_access
1 2.9412 main
可以看到bad_access() cache miss 事件的樣本共有33個,占總數的97%
改進 bad_access() 為 good_access() 后:
void good_access()
{
int k, j, sum = 0;
for(k = 0; k < 2047; k++)
for(j = 0; j < 7; j++)
sum += matrix[k][j] * 1024;
}
CPU: GODSON2E, speed 0 MHz (estimated)
Counted ICACHE_MISSES events (Instruction Cache misses number ) with a unit mask of 0x00 (No unit mask) count 500
samples % symbol name
22 95.6522 good_access
1 4.3478 main
可以看到改进后 cache miss 事件的样本减少为22个,占总数的95%