由于在硬件和软件之间有一些意料之外的交互,分析 Linux 操作系统和应用程序的代码可能是很困难的,但评测( profiling )办法可以识别出系统的性能问题。本文介绍的是 Oprofile,这是一种用于 Linux 的评测工具,将包含在即将发布的稳定内核中。
评测 是表示不同性能特性和特征的数据的形式化总结或分析,它通常以图形和表的形式的出现。评测表提供为特定的处理器事件收集的采样的百分数或数量,比如高速缓存线路故障的数量、传输后备缓存( TLB )故障的数量,等等。
Oprofile 是用于 Linux 的若干种评测和性能监控工具中的一种。它可以工作在不同的体系结构上,包括 IA32, IA64 和 AMD Athlon 系列。它的开销小,将被包含在(Linux)2.6 版的内核中。
Oprofile 可以帮助用户识别诸如循环的展开、高速缓存的使用率低、低效的类型转换和冗余操作、错误预测转移等问题。它收集有关处理器事件的信息,其中包括TLB的故障、停机、存储器访问、位于 DCU(数据高速缓存单元)中的总线路数、一个 DCU 故障的周期数,以及不可高速缓存的和可高速缓存的指令的获取数量。Oprofile是一种细粒度的工具,可以为指令集或者为函数、系统调用或中断处理例程收集采样。Oprofile 通过取样来工作。使用收集到的评测数据,用户可以很容易地找出性能问题。
安装 Oprofile
Oprofile 包含在 Linux 2.5 和更高版本的内核中,也包含在大多数较新的 Linux 版本中,包括 Red Hat 9 。用户也可以使用在本文后面 参考资料部分中的链接来下载 Oprofile 。用户需要在启用 Oprofile 的情况下重新编译内核。下面介绍具体做法:
- 启动Oprofile:
#cd /usr/src/linux #make xconfig/menuconfig
在评测菜单中启用 Oprofile ,在 .config 文件中设置
CONFIG_PROFILING=y和CONFIG_OPROFILE=y。 另外,还要在 Processor type and features 菜单中启用 Local APIC 和 IO-APIC 。 - 按下面命令格式重新编译:
启动新内核:#make dep (use for 2.4 kernel versions ) #make bzImage
- 为了配置和安装 Oprofile 实用工具,键入以下语句:
#./configure --with-linux=/usr/src/linux/ --with-qt-dir=/usr/lib/qt/ --with-kernel-support #make #make install
关于系统要求的信息和更加详细的安装指示,请参阅 参考资料部分中的链接。
Oprofile 工具概述:
- op_help: 列出可用的事件,并带有简短的描述
- opcontrol: 控制 Oprofile 的数据收集
- oprofpp: 检索有用的评测数据
- op_time: 为系统上的所有映像列出相关的评测值
- op_to_source: 产生带注解的源文件、汇编文件或源文件和汇编文件的混合
- op_merge: 合并属于同一个应用程序的采样文件
- op_import: 将采样数据库文件从外部格式(abi)转换为本地格式
启动评测的三个快速步骤:
- 启动 profiler(评测器):
# 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 - 现在评测器已经运行,用户可以开始做他们做的事情:
- 用下面的选项来转储被评测的数据:
# opcontrol --stop/--shutdown/--dump
Oprofile 分析:高速缓存利用率问题
高速缓存是最靠近处理器执行单元的存储器,它比主存储器容量小得多,也快得多。它可以在处理器芯片的内部,也可以在处理器芯片的外部。高速缓存中存放的是最频繁使用的指令和数据。由于允许对频繁使用的数据进行快速存取,软件运行要比从主存储器中存取数据快得多。在 Intel IA32 P4 中,数据被存储在每条线路 32 字节的高速缓存线路中。
对于多 CPU 的系统来说,当一个 CPU 修改在 CPU 之间共享的数据的时候,在CPU的高速缓存中的高速缓存线路是无效的。
如果数据或指令没有出现在高速缓存中,或者如果高速缓存线路无效的时候,CPU 通过从主存储器中读数据来更新它的高速缓存。负责做这件事情的处理器事件称为 L2_LINES_IN 。从主存储器读数据需要较多的 CPU 周期。Oprofile 可以帮助用户识别类似于清单 1 所列出的高速缓存问题。
/*
* 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, |
上面的程序是用来评测事件 L2_LINES_IN 的。请注意在 func1 和 main 中收集的采样:
清单 2. 用于 L2_LINES_IN 的 Oprofile 数据
# 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 |
现在使用 CPU_CLK_UNHALTED 事件来评测同一个应用程序(可执行文件),这个事件基本上就是收集无停顿地运行的 CPU 周期数的采样。在该例程中收集到的采样数量与处理器在执行指令时所花的时间成正比。收集的采样越多,处理器执行指令所花的时间就越多。请注意在 main 和 func1 中收集的采样数量:
清单 3. 为 CPU_CLK_UNHALTED 收集的 Oprofile 数据
#oprofpp -l ./appln Cpu type: PIII Cpu speed was (MHz estimation) : 699.667 Counter 0 counted CPU_CLK_UNHALTED events |
为了改善性能,现在我们把共享数据结构的两个元素分离到不同的高速缓存线路,从而优化共享的数据结构。在 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. 经过优化的数据结构
