用 OProfile 彻底了解性能(2)启动评测的三个快速步骤
- UID
- 1066743
|
用 OProfile 彻底了解性能(2)启动评测的三个快速步骤
启动评测的三个快速步骤:- 启动 profiler(评测器):
1
2
3
4
| # opcontrol --setup --ctr0-event=CPU_CLK_UNHALTED
--ctr0-count=600000 --vmlinux=/usr/src/linux-2.4.20/vmlinux
For RTC mode users, use --rtc-value=2048
# opcontrol --start
|
- 现在评测器已经运行,用户可以开始做他们做的事情:
- 用下面的选项来转储被评测的数据:
1
| # opcontrol --stop/--shutdown/--dump
|
Oprofile 分析:高速缓存利用率问题高速缓存是最靠近处理器执行单元的存储器,它比主存储器容量小得多,也快得多。它可以在处理器芯片的内部,也可以在处理器芯片的外部。高速缓存中存放的是最频繁使用的指令和数据。由于允许对频繁使用的数据进行快速存取,软件运行要比从主存储器中存取数据快得多。在Intel IA32 P4 中,数据被存储在每条线路 32 字节的高速缓存线路中。
对于多 CPU 的系统来说,当一个 CPU 修改在 CPU 之间共享的数据的时候,在CPU的高速缓存中的高速缓存线路是无效的。
如果数据或指令没有出现在高速缓存中,或者如果高速缓存线路无效的时候,CPU 通过从主存储器中读数据来更新它的高速缓存。负责做这件事情的处理器事件称为 L2_LINES_IN 。从主存储器读数据需要较多的 CPU 周期。Oprofile 可以帮助用户识别类似于清单1 所列出的高速缓存问题。
清单 1. 存在高速缓存问题的程序代码
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
| /*
* Shared data being modified by two threads running on different CPUs.
*/
/* shared structure between two threads which will be optimized later*/
struct shared_data_align {
unsigned int num_proc1;
unsigned int num_proc2;
};
/*
* Shared structure between two threads remains unchanged (non optimized)
* This is required in order to collect some samples for the L2_LINES_IN event.
*/
struct shared_data_nonalign {
unsigned int num_proc1;
unsigned int num_proc2;
};
/*
* In the example program below, the parent process creates a clone
* thread sharing its memory space. The parent thread running on one CPU
* increments the num_proc1 element of the common and common_aln. The cloned
* thread running on another CPU increments the value of num_proc2 element of
* the common and common_aln structure.
*/
/* Declare global data */
struct shared_data_nonalign common_aln;
/*Declare local shared data */
struct shared_data_align common;
/* Now clone a thread sharing memory space with the parent process */
if ((pid = clone(func1, buff+8188, CLONE_VM, &common)) < 0) {
perror("clone");
exit(1);
}
/* Increment the value of num_proc1 in loop */
for (j = 0; j < 200; j++)
for(i = 0; i < 100000; i++) {
common.num_proc1++;
}
/* Increment the value of num_proc1 in loop */
for (j = 0; j < 200; j++)
for(i = 0; i < 100000; i++) {
common_aln.num_proc1++;
}
/*
* The routine below is called by the cloned thread, to increment the num_proc2
* element of common and common_aln structure in loop.
*/
int func1(struct shared_data_align *com)
{
int i, j;
/* Increment the value of num_proc2 in loop */
for (j = 0; j < 200; j++)
for (i = 0; i < 100000; i++) {
com->num_proc2++;
}
/* Increment the value of num_proc2 in loop */
for (j = 0; j < 200; j++)
for (i = 0; i < 100000; i++) {
common_aln.num_proc2++;
}
}
|
上面的程序是用来评测事件 L2_LINES_IN 的。请注意在 func1 和 main 中收集的采样:
清单 2. 用于 L2_LINES_IN 的 Oprofile 数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # opcontrol --setup --ctr0-event=L2_LINES_IN
--ctr0-count=500 --vmlinux=/usr/src/linux-2.4.20/vmlinux
#opcontrol --start
#./appln
#opcontrol --stop
#oprofpp -l ./appln
Cpu type: PIII
Cpu speed was (MHz estimation) : 699.57
Counter 0 counted L2_LINES_IN events (number of allocated lines in L2) with a
unit mask of 0x00 (No unit mask) count 500
vma samples % symbol name
080483d0 0 0 _start
080483f4 0 0 call_gmon_start
08048420 0 0 __do_global_dtors_aux
08048480 0 0 fini_dummy
08048490 0 0 frame_dummy
080484c0 0 0 init_dummy
08048630 0 0 __do_global_ctors_aux
08048660 0 0 init_dummy
08048670 0 0 _fini
080484d0 4107 49.2033 main
080485b8 4240 50.7967 func1
|
现在使用 CPU_CLK_UNHALTED 事件来评测同一个应用程序(可执行文件),这个事件基本上就是收集无停顿地运行的CPU 周期数的采样。在该例程中收集到的采样数量与处理器在执行指令时所花的时间成正比。收集的采样越多,处理器执行指令所花的时间就越多。请注意在 main 和 func1 中收集的采样数量:
清单 3. 为 CPU_CLK_UNHALTED 收集的 Oprofile 数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| #oprofpp -l ./appln
Cpu type: PIII
Cpu speed was (MHz estimation) : 699.667
Counter 0 counted CPU_CLK_UNHALTED events (clocks processor is not halted) with
a unit mask of 0x00 (No unit mask) count 10000
vma samples % symbol name
080483d0 0 0 _start
080483f4 0 0 call_gmon_start
08048420 0 0 __do_global_dtors_aux
08048480 0 0 fini_dummy
08048490 0 0 frame_dummy
080484c0 0 0 init_dummy
08048640 0 0 __do_global_ctors_aux
08048670 0 0 init_dummy
08048680 0 0 _fini
080484d0 40317 49.9356 main
080485bc 40421 50.0644 func1
|
为了改善性能,现在我们把共享数据结构的两个元素分离到不同的高速缓存线路,从而优化共享的数据结构。在 Intel IA32 P4 处理器中,每条L2 高速缓存线路的大小是 32 个字节。通过填充 shared_data_align 结构中的第一个元素的28个字节,该结构的元素可以被分离到两个不同的高速缓存线路。现在,父线程修改 shared_data_align 的 num_proc1 ,这导致在首次存取时从 1号 CPU 的高速缓存线路上读入 num_proc1 。将来父线程对 num_proc1 的存取会导致从该高速缓存线路的数据读入。克隆的线程修改 shared_data_align 的 num_proc2 ,这将导致在 2 号 CPU 的另一条高速缓存线路上获得 num_proc2 。 这两个并行运行的线程分别修改位于不同高速缓存线路上元素 num_proc1 和 num_proc2 。通过把该数据结构的两个元素分离到两条不同的高速缓存线路,一条高速缓存线路的修改就不会导致再次从存储器读入另外一条高速缓存线路。这样,被读入的高速缓存线路的数量就减少了。
清单 4. 经过优化的数据结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| /*
* The padding is added to separate the two unsigned ints in such a
* way that the two elements num_proc1 and num_proc2 are on two
* different cache lines.
*/
struct shared_data_align {
unsigned int num_proc1;
char padding[28];
unsigned int num_proc2;
};
/*
* This structure remains unchanged, so that some cache lines
* read in can be seen in profile data.
*/
struct shared_data_nonalign {
unsigned int num_proc1;
unsigned int num_proc2;
};
|
注意, shared_data_nonalign 还没有被优化。
既然您已经启用并运行了 Oprofile(已经这样做了,不是吗?),现在您可以试着自己执行下面的一些评测:为事件 L2_LINES_IN 收集 Oprofile 数据,并且将计数器设置为 500,如 清单2 所示。
还要尝试为事件 CPU_CLK_UNHALTED 收集 Oprofile 数据,同时将计数设置为 10000。对用优化的和未经优化的方法收集的数据加以比较,并且注意性能的改善。 |
|
|
|
|
|