原文地址: https://www.ibm.com/developerworks/cn/aix/library/1206_yudh_unixproblemsolve/index.html
同時通過對下面兩個例子的介紹,鞏固了上面問題分析的介紹:
- 一個多線程應用的性能問題的分析
- 一個 crash 問題的分析
UNIX 程序常見問題分類
UNIX 下運行程序,經常會遇到以下幾類問題 :
- Crash
- 內存泄露
- 句柄泄露
- 進程不響應
- 性能不滿足預期
- 邏輯錯誤
UNIX 程序常見問題的分析方法
UNIX 下 Crash 問題的分析方法
crash 原理和 core 文件生成原因 ( 信號的介紹 )
Crash 是進程崩潰,是由於應用進程做了錯誤的操作 ( 例如,數組拷貝越界導致對系統內存進行了寫操作,使用了錯誤的指針地址 ), 操作系統嚮應用進程發送了信號,如果應用進程沒有做特殊處理,應用進程將 core dump 在進程當前的工作目錄下生成一個 core 文件,core 文件複製了該進程的存儲圖像,是一個內存映像。
不是所有的信號默認行爲都是 crash, 常見默認 crash 信號主要有:
1
2
3
4
5
|
SIGABRT
SIGBUS
SIGSEGV
SIGILL
SIGPIPE
|
可以通過 kill –l (適用所有 UNIX 平臺)查看信號的信息。
查看針對某個進程的所有信號的默認行爲(例如:在 Solaris 平臺使用 psig pid 命令查看,其他平臺的命令略有不同,請參考各自平臺用戶手冊).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
25040: /qatest/ModelerServer/5.0.0.0.64/modelersrv_15_0 -server
HUP caught 0x10002958c 0
INT caught 0x100029580 0
QUIT default
ILL default
TRAP default
ABRT default
EMT default
FPE default
KILL default
BUS default
SEGV default
SYS default
PIPE ignored
ALRM default
TERM caught 0x100029580 0
USR1 default
USR2 default
CLD caught 0x100067f44 NOCLDSTOP
|
下面列舉一些常見信號的默認操作以及可能產生的原因:
例如:Solaris 平臺如下。下面的信息參考 Solaris 內核結構第 2 版第二章(Solaris 進程模型) 第 75 頁,其他平臺基本相同,請參考各自平臺用戶手冊:
信號 值 處理動作 發出信號的原因
SIGHUP 缺省的動作是終止進程 終端掛起或者控制進程終止
SIGINT 缺省的動作是終止進程 鍵盤中斷(如 break 鍵被按下)
SIGQUIT 缺省的動作是終止進程並進行內核映像轉儲(dump core)鍵盤的退出鍵被按下
SIGILL 缺省的動作是終止進程並進行內核映像轉儲(dump core)非法指令
SIGABRT 缺省的動作是終止進程並進行內核映像轉儲(dump core)由 abort(3) 發出的退出指令
SIGFPE 缺省的動作是終止進程並進行內核映像轉儲(dump core)浮點異常
SIGKILL 9 AEF Kill 信號 終止信號
SIGSEGV 缺省的動作是終止進程並進行內核映像轉儲(dump core)無效的內存引用
SIGPIPE 缺省的動作是終止進程 管道破裂 : 寫一個沒有讀端口的管道
SIGALRM 缺省的動作是終止進程 由 alarm(2) 發出的信號
SIGTERM 缺省的動作是終止進程 終止信號
SIGUSR1 缺省的動作是終止進程 用戶自定義信號 1
SIGUSR2 缺省的動作是終止進程 用戶自定義信號 2
SIGCHLD 缺省的動作是忽略此信號 子進程結束信號
SIGSTOP DEF 終止進程
SIGBUS 缺省的動作是終止進程並進行內核映像轉儲(dump core)總線錯誤 ( 錯誤的內存訪問 )
core 文件分析一般思路
首先使用 file 命令(所有 UNIX 平臺適用)查看 core 文件生成的源程序
1
2
|
bash-3.00$ file core
core: ELF 64-bit MSB core file SPARCV9 Version 1, from 'qatest'
|
從以上結果可以看出,該 core 文件是由 64 位程序 qatest 生成的。
然後使用 gdb( 或者 dbx) 對 core 文件進行分析:
1
|
bash-2.05$ dbx ./qatest ./core
|
再使用 where 命令查看 core 的位置:
1
2
3
4
|
Current function is MCXML_700::MCSetting::MCSetting
87 fpValue = s.GetValue()->Clone();
(dbx) where
|
從這個 core 文件可以看到,它收到了 BUS 信號,crash 的位置在 = s.GetValue()->Clone() 函數。
更多有關 gdb,dbx 的使用請參考 gdb,dbx 用戶手冊。
core 文件無法生成常見原因
當程序崩潰時,並不是總會生成 core 文件。經常有下面的情況導致 core 文件沒有產生:
- 對 core 文件大小做了限制,可以通過 ulimit(所有 UNIX 平臺適用)的命令進行查看:
1
2
3
4
5
6
7
8
9
10
|
bash-3.00$ ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
open files (-n) 256
pipe size (512 bytes, -p) 10
stack size (kbytes, -s) unlimited
cpu time (seconds, -t) unlimited
max user processes (-u) 29995
virtual memory (kbytes, -v) unlimited
|
建議使用下面的命令將這個限制改爲 unlimited
1
|
bash-3.00$ ulimit –c unlimited
|
- 磁盤空間是否充足,通過 df 命令(所有 UNIX 平臺適用)查看 Available 的空間是否充足。
1
2
3
4
|
bash-3.00$ df -k
Filesystem 1024-blocks Used Available Capacity Mounted on
/ 0 40975117 99394509 30% /
/dev 140369626 40975117 99394509 30% /dev
|
- 查看信號是否被捕獲(例如:Solaris 平臺使用 psig 進行查看,見上面的例子,其他平臺的命令略有不同,請參考各自平臺用戶手冊)。
如果上面的情況導致 core 文件沒有生成,請修改它。
- 沒有 core 文件產生,如何分析 crash
有時候經常發現進程 crash 了,但是 core dump 文件沒有產生。這時可以使用 dbx,gdb 等調試工具首先 attach 到運行的進程上,然後再執行業務,如果進程 crash,dbx 或者 gdb 將終止在 crash 的位置,我們便可以根據這個堆棧信息對 crash 進行分析,與分析 core 文件相同。
UNIX 下內存泄露問題分析方法
內存泄露簡單的說就是申請了一塊內存空間,使用完畢後沒有釋放掉。它的一般表現方式是程序運行時間越長,佔用內存越多,最終用盡全部內存,整個系統崩潰。
封裝 new 和 delete 對內存泄漏進行分析
通過對 new 和 delete 的封裝,將 new 和 delete 的過程通過日誌文件的保存記錄下來。然後對日誌文件進行分析,是否 new 和 delete 是匹配的,有哪些內存申請,但是沒有釋放。
下面通過一個簡單的測試程序(此代碼使用 C++ 語言實現,目前沒有考慮申請數組的情況)進行演示:
這個測試程序申請了 pTemp1,pTemp2,pTemp3 的三塊內存,但是僅僅釋放了 pTemp3,存在 pTemp1 和 pTemp2 的內存泄露。
程序解釋:
在每次內存申請時,將內存申請的信息註冊到 MAP 表中,在每次內存釋放時,將對應的內存信息從註冊表中刪除,這樣註冊表中將保存未釋放的內存信息,按照一定的規則將註冊表中的信息輸出(定時或者進程退出等)。然後我們從輸出信息中便可以分析出內存泄漏點。
通過自定義宏 DEMONEW 和 DEMODELETE 申請內存和釋放內存,在這兩個宏中,我們將內存的申請和釋放做了記錄,從而可以得到未釋放內存的信息,請參考下面的程序實現流程圖:
圖 1. 內存申請釋放流程:
圖 2.DEMONEW 實現流程:
圖 3.DEMODELETE 實現流程:
測試程序代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
#include <
map
>
#include <
iostream
>
#include <
string
>
#include <
fstream
>
// 申請內存時,存儲 new 位置的數據結構
typedef struct {
std::string filename;
int line;
} MEMINFO;
// 輸出文件
std::ofstream loginfo("//tmp/memory.log");
typedef std::map<
long
long, MEMINFO> MemMap;
// 存儲內存申請記錄(在每次內存申請時,將內存申請的地址作爲鍵值,
// 內存申請操作所在的文件名和行號作爲內容,存儲到下面的數據結構 memmap 中)
MemMap memmap;
// 註冊內存申請信息到上面的 map 容器中,輸入的參數分別爲內存地址,文件名,行號
void RegMemInfo(long long addr, const char *fname, long long lnum)
{
MEMINFO info;
if (fname)
{
info.filename = fname;
}
info.line = lnum;
memmap.insert(MemMap::value_type(addr, info));
};
// 卸載內存申請信息從上面的 map 容器中,輸入的參數爲內存地址
void UnRegMemInfo(long long addr)
{
if (memmap.end() != memmap.find(addr))
{
memmap.erase(addr);
}
}
// 定義宏 DEMONEW,封裝了內存申請的操作,在內存申請成功後,調用 RegMemInfo 功能,
// 將內存信息註冊到 map 容器中
#define DEMONEW(p, ptype)\
do \
{\
p = new ptype;\
if (p)\
{\
RegMemInfo((long long)p, __FILE__, __LINE__);\
}\
else\
{\
std::cout<<"NEW failed"<<
std::endl
;\
}\
}\
while(0)
// 定義宏 DEMODELETE,封裝了內存釋放的操作,在內存釋放時,調用 UnRegMemInfo
// 功能,將內存信息從 map 容器中刪除
#define DEMODELETE(p) \
do\
{\
if (p)\
{\
UnRegMemInfo((long long)p);\
delete p;\
p
=
0
;\
}\
}while(0)
// 寫信息流內容到文件
void WriteString(std::string buf)
{
loginfo << buf <<std::endl;
}
// 將整數轉換爲字符串
std::string Int2Str(int value)
{
char buf[16] = {0};
sprintf(buf, "%d", value);
return buf;
}
// 輸出 map 容器中存儲的內存沒有釋放的信息
void Output()
{
loginfo.clear();
if (memmap.empty())
{
WriteString("No Memory leak.");
return;
}
MemMap::iterator iter;
WriteString("The Memory leak is below:");
for (
iter
=
memmap
.begin(); iter != memmap.end(); ++iter)
{
std::string buf;
std::string
sAddr
=
Int2Str
(iter->first);
std::string sLine = Int2Str(iter->second.line);
buf += "memory Address ";
buf += sAddr;
buf += ": FILE ";
buf += iter->second.filename;
buf += ", LINE ";
buf += sLine;
buf += " no freed";
WriteString(buf);
}
}
// 測試程序主入口函數
int main(int argc, char* argv[])
{
char* pTemp1 = 0;
DEMONEW(pTemp1, char);
char* pTemp2 = 0;
DEMONEW(pTemp2, char);
char* pTemp3 = 0;
DEMONEW(pTemp3, char);
DEMODELETE(pTemp1);
Output();
loginfo.close();
return 0;
}
|
上面測試程序的輸出是:
1
2
3
4
5
|
The Memory leak is below:
memory Address 280929008: FILE test.cpp, LINE 109 no freed
memory Address 280929152: FILE test.cpp, LINE 111 no freed
|
輸出分析:
從輸出結果我們可以發現,此測試程序在 test.cpp 文件的 109 和 111 行各有一處內存泄漏,查看源代碼,它們分別是 pTemp1 和 pTemp2。
使用 Purify(適用所有 UNIX 平臺)或者 valgrind(適用 Linux 平臺)工具對內存泄漏進行分析
- 使用 Purify 對內存泄漏進行分析
Purify 是 IBM Rational PurifyPlus 的工具之一, 是一個面向 VC、VB 或者 Java 開發的測試 Visual C/C++ 和 Java 代碼中與內存有關的錯誤的工具,它確保整個應用程序的質量和可靠性。在查找典型的 C/C++ 程序中的傳統內存訪問錯誤, Rational Purify 可以大顯身手。在 UNIX 系統中,使用 Purify 需要重新編譯程序。通常的做法是修改 Makefile 中的編譯器變量。
例如定義 CC 變量爲 purify gcc
首先運行 Purify 安裝目錄下的 purifyplus_setup.sh 來設置環境變量,然後運行 make 重新編譯程序。需要指出的是,程序必須編譯成調試版本。在編譯器命令(例如 Solaris 的 CC 編譯器,Linux 的 gcc 編譯器等)後,也就是必須使用"-g"選項。在重新編譯的程序運行結束後,Purify 會打印出一個分析報告。
測試程序(此代碼使用 C++ 語言實現):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <
stdlib.h
>
void func1()
{
//char* pBuf = new char;
}
void func2()
{
char* pBuf = new char;
}
void func3()
{
char* pBuf = new char;
}
int main()
{
func1();
func2();
func3();
return 0;
}
|
編譯程序:
Purify 輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
**** Purify instrumented ./tst1 (pid 530 at Fri Apr 6 16:50:59 2012)
* Purify 7.0.0.0-014 090319 Linux (64-bit) (C) Copyright IBM Corporation. 1992,
* 2009 All Rights Reserved.
* For contact information type: "purify -help"
* For Purify Viewer output, set the DISPLAY environment variable.
* License successfully checked out.
* Command-line: ./tst1
* Options settings: -g++=yes -purify \
-purify-home=
/home/dyu/purify/PurifyPlus.7.0.0.0-014/Rational/releases/\
purify.i386_linux2.7.0.0.0-014
-process-large-objects=yes -gcc3_path=/usr/bin/g++ \
-cache-dir=
/home/dyu/purify/PurifyPlus.7.0.0.0-014/Rational/releases/\
purify.i386_linux2.7.0.0.0-014\
/cache
**** Purify instrumented ./tst1 (pid 530) ****
Current file descriptors in use: 5
FIU: file descriptor 0: <
stdin
>
FIU: file descriptor 1: <
stdout
>
FIU: file descriptor 2: <
stderr
>
FIU: file descriptor 26: <
reserved
for Purify internal use>
FIU: file descriptor 27: <
reserved
for Purify internal use>
**** Purify instrumented ./tst1 (pid 530) ****
Purify: Searching for all memory leaks...
Memory leaked: 2 bytes (100%); potentially leaked: 0 bytes (0%)
MLK: 1 byte leaked at 0xa457098
* This memory was allocated from:
malloc [rtlib.o]
operator new(unsigned long) [libstdc++.so.6]
operator new(unsigned long) [rtlib.o]
func2() [tst.cpp:9]
main [tst.cpp:20]
__libc_start_main [libc.so.6]
_start [crt1.o]
MLK: 1 byte leaked at 0xa457138
* This memory was allocated from:
malloc [rtlib.o]
operator new(unsigned long) [libstdc++.so.6]
operator new(unsigned long) [rtlib.o]
func3() [tst.cpp:14]
main [tst.cpp:21]
__libc_start_main [libc.so.6]
_start [crt1.o]
Purify Heap Analysis (combining suppressed and unsuppressed blocks)
Blocks Bytes
Leaked 2 2
Potentially Leaked 0 0
In-Use 0 0
----------------------------------------
Total Allocated 2 2
|
Purify 圖形輸出:
安裝 Xmanager 等工具,設置 DISPLAY 爲本機 IP,見下圖:
輸出分析:
從 purify 的輸出可以看出,此測試程序存在兩處內存泄漏,它分別是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行。
- 使用 valgrind(現在僅僅支持 Linux 平臺)對內存泄漏進行分析
Valgrind 是一套 Linux 下,開放源代碼(GPL V2)的仿真調試工具的集合。Valgrind 由內核(core)以及基於內核的其他調試工具組成。內核類似於一個框架,它模擬了一個 CPU 環境,並提供服務給其他工具;而其他工具則類似於插件 (plug-in),利用內核提供的服務完成各種特定的內存調試任務。Valgrind 在對程序進行偵測的時候,不需要對程序進行重新編譯。
下面使用 valgrind 對一個簡單的測試程序進行。
測試程序:
同 Purify 的測試程序相同。
編譯程序:
valgrind 輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
==25396== Memcheck, a memory error detector
==25396== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==25396== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==25396== Command: ./tst
==25396==
==25396==
==25396== HEAP SUMMARY:
==25396== in use at exit: 2 bytes in 2 blocks
==25396== total heap usage: 2 allocs, 0 frees, 2 bytes allocated
==25396==
==25396== 1 bytes in 1 blocks are definitely lost in loss record 1 of 2
==25396== at 0x4A0666E: operator new(unsigned long) (vg_replace_malloc.c:220)
==25396== by 0x4005C7: func2() (tst.cpp:9)
==25396== by 0x4005DB: main (tst.cpp:20)
==25396==
==25396== 1 bytes in 1 blocks are definitely lost in loss record 2 of 2
==25396== at 0x4A0666E: operator new(unsigned long) (vg_replace_malloc.c:220)
==25396== by 0x4005AF: func3() (tst.cpp:14)
==25396== by 0x4005E0: main (tst.cpp:21)
==25396==
==25396== LEAK SUMMARY:
==25396== definitely lost: 2 bytes in 2 blocks
==25396== indirectly lost: 0 bytes in 0 blocks
==25396== possibly lost: 0 bytes in 0 blocks
==25396== still reachable: 0 bytes in 0 blocks
==25396== suppressed: 0 bytes in 0 blocks
==25396==
==25396== For counts of detected and suppressed errors, rerun with: -v
==25396== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)
|
輸出分析:
從 valgrind 的輸出可以看出,此測試程序存在兩處內存泄漏,它分別是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行,與 purify 的檢測結果相同。
UNIX 下進程性能問題分析方法
- 檢查 CPU 佔用情況(包含單個線程的 CPU 使用情況)
下面分別對 Solaris 和 Linux 平臺做了舉例,其他平臺的命令略有不同,請參考各自平臺用戶手冊。
例如:在 Solaris 平臺
使用 prtdiag 命令查看系統 CPU 信息:
1
|
[/rnd/homes/builder1/purify >prtdiag
|
輸出信息:
1
2
3
4
5
6
7
8
9
|
SystemConfiguration:SunMicrosystemssun4uSunFireV210
Systemclockfrequency:167MHZ
Memorysize:8GB
==================================== CPUs ====================================
E$ CPU CPU Temperature
CPU Freq Size Implementation Mask Die Amb. Status Location
--- -------- ---------- -------------------- ----- ---- ---- ------ --------
0 1002 MHz 1MB SUNW,UltraSPARC-IIIi 2.3 - - online MB/P0
1 1002 MHz 1MB SUNW,UltraSPARC-IIIi 2.3 - - online MB/P1
|
輸出分析:
從上面的輸出可以發現,此服務器有兩個 CPU,以及各個 CPU 的信息。
使用 prstat 命令進程中每個線程的 CPU 使用情況:
1
|
[/rnd/homes/builder1/purify >>prstat -Lu root
|
輸出信息:
1
2
3
|
PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/LWPID
230 root 3896K 3072K sleep 59 0 0:04:37 0.0% nscd/18
26229 root 201M 118M sleep 59 0 4:38:06 0.0% java/4
|
輸出分析:
LWPID 雖然不是線程 ID,但是在 Solaris10 版本與線程 ID 是一一對應關係,所以可以通過 LWPID 進行分析。
例如:在 Linux 平臺
查看 CPU 個數 ( 使用 top 命令,然後按 1 鍵可顯示 CPU 的個數以及每個 CPU 的負載情況 ):
輸出信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Tasks: 205 total, 7 running, 196 sleeping, 1 stopped, 1 zombie
Cpu0 : 92.7%us, 7.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 94.2%us, 5.8%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 2033948k total, 1867908k used, 166040k free, 20088k buffers
Swap: 4095992k total, 393420k used, 3702572k free, 389476k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3088 root 19 0 136m 73m 6960 R 57.2 3.7 0:00.89 cc1plus
3082 root 21 0 40580 33m 4732 R 52.7 1.7 0:00.75 cc1plus
3068 root 25 0 232m 163m 10m R 45.2 8.3 0:03.69 cc1plus
3085 root 19 0 98.8m 33m 5696 R 28.6 1.7 0:00.24 cc1plus
2901 root 25 0 89732 83m 4508 R 15.1 4.2 0:09.40 cc1plus
3069 dyu 15 0 10884 1120 768 R 1.5 0.1 0:00.04 top
1 root 15 0 10372 380 348 S 0.0 0.0 0:03.19 init
2 root RT -5 0 0 0 S 0.0 0.0 0:00.00 migration/0
3 root 34 19 0 0 0 S 0.0 0.0 0:00.15 ksoftirqd/0
4 root RT -5 0 0 0 S 0.0 0.0 0:00.00 watchdog/0
|
輸出分析:
從上面輸出可以得到,此服務器有兩個 CPU,均爲滿負荷工作,空閒的 CPU 使用均爲 0%。
查看進程中每個線程的 CPU 使用情況:
--sort=%cpu 命令輸出要求按 CPU 的使用多少進行排序輸出。
輸出信息:
1
2
3
4
5
6
7
8
9
|
USER PID PPID TID TIME %CPU CMD
root 2 1 2 00:00:00 0.0 [migration/0]
root 3 1 3 00:00:00 0.0 [ksoftirqd/0]
root 4 1 4 00:00:00 0.0 [watchdog/0]
root 7 1 7 00:00:00 0.0 [watchdog/1]
root 10 1 10 00:00:00 0.0 [khelper]
root 27 1 27 00:00:00 0.0 [kthread]
root 34 27 34 00:00:00 0.0 [kacpid]
|
輸出分析:
從上面輸出可以根據每個線程的 CPU 使用情況分析,性能瓶頸在哪個線程。
檢查 IO 使用 iostat 命令可以查看系統 IO 狀態等信息。
例如:Solaris 平臺 iostat 輸出:
1
2
3
4
5
6
7
|
/rnd/homes/builder1/purify >>iostat 1
tty sd0 sd1 nfs1 nfs2 cpu
tin tout kps tps serv kps tps serv kps tps serv kps tps serv us sy wt id
0 6 12 2 9 0 0 27 0 0 0 4 0 8 2 1 0 97
0 234 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 99
0 80 1 1 11 0 0 0 0 0 0 0 0 0 0 5 0 95
0 80 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 99
|
上面是 iostat 的一個簡單輸出,可以查看 iostat 的幫助(man iostat)瞭解更多信息。
- 使用 Quantify 對程序性能進行分析
Rational Quantify 是 IBM Rational PurifyPlus 的工具之一,可以按多種級別(包括代碼行級和函數級)測定性能,並提供分析性能改進所需的準確和詳細的信息,使您可以覈實代碼性能確實有所提高。使用 Rational Quantify,您可以更好地控制數據記錄的速度和準確性。您可以按模塊調整工具收集信息的級別: 對於應用程序中感興趣的那部分,可以收集詳細信息;而對於不太重要的模塊,您可以加快數據記錄的速度並簡化數據收集。使用「運行控制工具欄」,可以直接、實時地控制性能數據的記錄。既可以收集應用程序在整個運行過程中的性能數據,也可以只收集程序執行過程中您最感興趣的某些階段的性能數據。
下面是一個使用 Quantify 對程序性能進行分析的例子
測試程序(此代碼使用 C++ 語言實現):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include <
stdlib.h
>
#include <
iostream
>
void func1()
{
for (int i = 0; i < 10000; ++i)
{
char* pBuf = new char[1024];
delete []pBuf;
}
}
void func2()
{
for (int i = 0; i < 10000; ++i)
{
char* pBuf = new char[1024];
delete []pBuf;
}
}
void func3()
{
for (int i = 0; i < 100; ++i)
{
std::cout << "Hello World" << std::endl;
}
}
int main()
{
func1();
func2();
func3();
return 0;
}
|
編譯程序:
Quantify 輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
**** Quantify instrumented ./performancetst (pid 18503 at 20:12:14)
Quantify 7.0.0.0-014 090319 Linux (64-bit) (C) Copyright IBM Corporation. 1992,
2009 All Rights Reserved.
* For contact information type: "quantify -help"
* License successfully checked out.
Quantify: Sending data for 178 of 5713 functions
from ./performancetst (pid 18772).........done.
Quantify: Resource Statistics for ./performancetst (pid 18772)
* cycles secs
* Total counted time: 56984465 0.031 (100.0%)
* Time in your code: 5599024 0.003 ( 9.8%)
* Time in system calls: 51252884 0.027 ( 89.9%)
* Dynamic library loading: 132557 0.000 ( 0.2%)
*
* Note: Data collected assuming a machine type of Intel-Core with
* clock rate of 1.867 GHz.
* Note: These times exclude Quantify overhead and possible memory effects.
*
* Elapsed data collection time: 0.383 secs
*
* Note: This measurement includes Quantify overhead.
*
To view your saved Quantify data, type:
quantify -view /home/dyu/purify/performancetst.18772.0.qv
|
Quantify 的圖形輸出:
安裝 Xmanager 等工具,設置 DISPLAY 爲本機 IP,見下圖:
輸出分析:
從 Quantify 的輸出可以對程序的性能進行初步分析,func1 時間花費爲 43.14%,func2 爲 42.59%,func3 爲 14.27%。
示例演示
通過兩個實例去演示多線程性能問題和產品不兼容導致 crash 的問題。
一個多線程互斥導致性能瓶頸問題
- 問題描述:
對某個多線程程序,當單線程的情況下,執行任務 1 花費 70s,但是當配置爲 16 個線程時,執行任務 1 仍然花費時間大約 70s。
- 問題分析:
首先查看單個線程或多個線程的 CPU 使用情況:
1
2
|
PID USERNAME THR PRI NICE SIZE RES STATE TIME CPU COMMAND
11248 czhou 7 0 0 556M 555M cpu/1 0:17 6.25% CFTestApp
|
1
2
3
4
5
6
|
當多線程情況下,查看每個線程的 CPU 佔用情況:
PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/LWPID
11248 czhou 3606M 3605M cpu1 0 0 0:07:11 6.25% CFTestApp/12
11248 czhou 3606M 3605M cpu1 0 0 0:07:11 0% CFTestApp/1
11248 czhou 3606M 3605M cpu1 0 0 0:07:11 0% CFTestApp/5
11248 czhou 3606M 3605M cpu1 0 0 0:07:11 0% CFTestApp/7
|
從上面可以發現。當多線程的情況下,在 16 個線程中僅僅一個線程的 CPU 佔用 6.25%,其他線程佔用均爲 0%。可以斷定大多數的線程被 block 住。然後需要查看這個進程的堆棧信息,得到每個線程的函數調用關係。
1
2
3
4
5
6
7
8
9
10
11
12
|
Pstack 11248
----------------- lwp# 1 / thread# 1 --------------------
ff11af60 ???(ffbff8e8, ffbff8e0)
ff15e328 dynamic_cast(258, 7, 10f88, 0, 139c0, 21508) + 58
0001110c main (1, ffbff9d4, ffbff9dc, 21400, 0, 0) + fc
00010b58 _start (0, 0, 0, 0, 0, 0) + 108
----------------- lwp# 7 --------------------------------
ff11af60 ???(fef7ff30, fef7ff28)
ff15e328 dynamic_cast(1, ff010224, 0, 0, 0, 0) + 58
00010fd8 __1cLwork_thread6Fpv_0_ (0, 0, 0, 0, 0, 0) + 8
ff165e48 _lwp_start (0, 0, 0, 0, 0, 0)
Then I remove the dynamic_cast, the problem was resolved.
|
從上面的線程堆棧信息,我們可以看到,大部分的線程幾乎都 block 在 dynamic_cast 函數。
-
(3)問題解決:
針對上面的分析對這個性能瓶頸代碼進行修正。
一個由於產品不兼容導致 crash 的問題
- 問題描述:
在 Linux 平臺,產品 A 使用編譯器 icpc 編譯,產品 B 使用編譯器 g++ 編譯。進程 C 會同時加載產品 A 與產品 B,當進程 C 運行時調用產品 A 中的函數 funcA 時 crash 發生。
- 問題分析:
從 core 文件,我們可以得到下面的信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
(gdb) where
#0 std::_Rb_tree<
int
, std::pair<int const, int>,
std::_Select1st<
std::pair
<int const,
int> >, std::less<
int
>, std::allocator<
std::pair
<int const, int> > >
::_M_end (this=0x602a20)
at /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../include/c++/4.1.2/bits/stl_tree.h:477
#1 0x0000000000400d3c in std::_Rb_tree<
int
, std::pair<int const, int>,
std::_Select1st<
std::pair
<int const, int> >, std::less<
int
>,
std::allocator<
std::pair
|