程序性能分析与多处理器性能优化全解析
在程序开发过程中,性能优化是一个至关重要的环节。了解程序的运行性能,找出性能瓶颈,是提升程序效率的关键。下面我们将介绍几种常用的性能分析工具,并探讨多处理器系统的性能特点和相关硬件类型。
性能分析工具
在分析程序性能时,我们常常需要借助一些工具。例如,当代码中没有详细的注释来帮助我们了解程序的运行情况时,就可以使用像
gcov
和
gprof
这样的工具。
假设我们有一个程序,运行
gcov
后可以发现某些代码行的调用次数。不过要确定程序具体在哪些地方花费了大量时间,
gprof
就能发挥重要作用。例如,对于一个名为
summer-proj
的程序,我们可以使用以下命令:
$ gprof --no-graph -l summer-proj| head -10
输出结果如下:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls Ts/call Ts/call name
34.00 0.17 0.17 main (summer-proj.c:22 @ 8048926)
20.00 0.27 0.10 main (summer-proj.c:25 @ 8048879)
16.00 0.35 0.08 main (summer-proj.c:19 @ 80488c6)
11.00 0.41 0.06 main (summer-proj.c:18 @ 80488b3)
6.00 0.43 0.03 main (summer-proj.c:21 @ 8048870)
...
从这个输出中,我们可以看到每行代码所花费的时间,并对其进行排名。这表明,虽然
gcov
显示第 19 行代码调用次数最多,但实际上程序大部分时间花费在了第 22 行。在大型复杂程序中,
gcov
的输出通常能很好地指示代码的性能瓶颈所在。
然而,还有一个更强大的工具——
OProfile
。它是 Linux 内核中内置的一个性能分析器,具有诸多优势。
OProfile 介绍
OProfile
是 Linux 内核的一个内置分析器,但要使用它,内核必须在编译时启用
OProfile
支持。虽然它对内核开发者确定内核的性能瓶颈非常有用,但对于普通程序员来说,它也能提供用户空间的性能信息。而且,使用
OProfile
无需重新编译代码,也不需要内核源代码或经过特殊处理的目标文件。
OProfile
由一组用户空间工具组成,包括一个守护进程和一些命令行工具:
-
opcontrol
:用于启动和停止
oprofiled
守护进程。
-
opreport
:根据最新的
oprofiled
统计信息生成报告。
-
opgprof
:生成一个
gmon.out
文件,供
gprof
使用。
-
opannotate
:根据分析数据生成带注释的源代码。
使用 opcontrol 控制分析器
opcontrol
有许多默认设置,使性能分析变得简单。同时,它也提供了丰富的选项,以满足更高级的需求。每次使用
opcontrol
时,它会保存命令行选项,下次使用时这些选项将成为默认值。
启动
oprofiled
守护进程时,首次调用
opcontrol
需要指定是分析内核还是仅分析用户空间。例如,如果只分析用户空间,可以使用以下命令:
$ sudo opcontrol --start --no-vmlinux
输出结果如下:
Using default event: GLOBAL_POWER_EVENTS:100000:1:1:1
Using 2.6+ OProfile kernel interface.
Using log file /var/lib/oprofile/oprofiled.log
Daemon started.
Profiler running.
opcontrol
会将最近的设置保存在用户主目录下的
.oprofile
目录中。下次启动
OProfile
时,只需使用
--start
选项即可。
当守护进程运行时,它会收集系统中所有运行程序的统计信息。要查看这些信息,需要先使用
--dump
选项将统计信息导出,然后可以使用
opreport
、
opgprof
或
opannotate
查看。例如:
$ opcontrol --dump
$ opreport | head -15
输出结果示例:
CPU: P4 / Xeon, speed 1700.38 MHz (estimated)
Counted GLOBAL_POWER_EVENTS events (time during which processor is not stopped)
with a unit mask of 0x01 (mandatory) count 100000
GLOBAL_POWER_E...|
samples| %|
------------------
29141 62.2445 no-vmlinux
Placeholder for kernel events
3380 7.2196 libc-2.3.3.so
2641 5.6411 libglib-2.0.so.0.400.8
2234 4.7718 libgobject-2.0.so.0.400.8
1824 3.8960 libgdk-x11-2.0.so.0.400.13
1076 2.2983 libvte.so.4.4.0
911 1.9459 libgtk-x11-2.0.so.0.400.13
861 1.8391 libcrypto.so.0.9.7a
738 1.5764 libpthread-2.3.3.so
659 1.4076 libX11.so.6.2
...
这个输出列出了在分析期间运行的代码所在的目标文件,并按出现频率排序。由于没有分析内核,内核模式下的样本会显示为
no-vmlinux
。
需要注意的是,
OProfile
使用 CPU 内置的性能计数器,对系统的影响较小。
oprofiled
从调用
opcontrol --start
开始记录信息,调用
opcontrol --dump
会生成统计信息的快照,但它会继续收集和累积数据,直到调用
opcontrol --stop
才停止收集。
使用 OProfile 分析应用程序
使用
OProfile
分析应用程序的步骤如下:
1. 启动守护进程并开始收集数据:
$ opcontrol --start --no-vmlinux
- (可选)重置统计信息:
$ opcontrol --reset
- 运行要分析的应用程序:
$ ./profme
- 导出统计信息并停止守护进程(可选):
$ opcontrol --dump
- 使用报告工具查看结果:
$ opreport
例如,对于一个编译后的程序
profme
,运行
opreport
后可能得到如下结果:
Counted GLOBAL_POWER_EVENTS events (time during which processor is not stopped)
with a unit mask of 0x01 (mandatory) count 100000
GLOBAL_POWER_E...|
samples| %|
------------------
16477 52.9807 libm-2.3.3.so
11597 37.2894 profme
1795 5.7717 no-vmlinux
325 1.0450 libc-2.3.3.so
162 0.5209 bash
140 0.4502 libglib-2.0.so.0.400.8
103 0.3312 oprofiled
从这个结果可以看出,应用程序大部分时间花费在标准数学库
libm
中。
此外,
OProfile
还提供了
opgprof
工具,可生成
gmon.out
文件供
gprof
使用:
$ opgprof profme
$ gprof --no-graph profme
不过,
opgprof
生成的
gmon.out
文件没有调用图信息,因此需要使用
--no-graph
选项。而且,这个输出结果只有样本计数,没有时间信息和调用次数。
如果需要逐行分析代码,需要在编译时加上
-g
选项。例如:
$ cc -g -O2 -o summer-proj summer-proj.c -lm
$ opcontrol --reset
$ opcontrol --start
$ ./summer-proj
$ opcontrol --dump
$ opgprof ./summer-proj
$ gprof --no-graph -l ./summer-proj | head
最后,
OProfile
的
opannotate
工具可以生成带注释的源代码,无需
opgprof
步骤:
$ opannotate --source ./summer-proj
输出结果如下:
/*
* Command line: opannotate --source ./summer-proj
*
* Interpretation of command line:
* Output annotated source file with samples
* Output all files
*
* CPU: P4 / Xeon, speed 1700.38 MHz (estimated)
* Counted GLOBAL_POWER_EVENTS events (time during which processor is not
stopped) with a unit mask of 0x01 (mandatory) count 100000
*/
/*
* Total samples for file : "/home/john/examples/ch-07/prof/summer-proj.c"
*
* 7458 99.8527
*/
:#include <stdio.h>
:#include <string.h>
:#include <stdlib.h>
:#include <time.h>
:#include <math.h>
:
:volatile double x;
:
:int main(int argc, char *argv[])
:{ /* main total: 7458 99.8527 */
: int i;
1554 20.8060 : for (i = 0; i < 16000000; i++) {
27 0.3615 : x = 1000.0;
:
: /* 0 <= r < 16 */
6 0.0803 : int r = i & 0xf;
:
115 1.5397 : if (r <= 8) {
4678 62.6322 : x = pow(x, 1.234); /* called 9/16 of the time. */
: }
350 4.6860 : else if (r <= 11) {
612 8.1939 : x = sqrt(x); /* called 3/16 of the time. */
: }
: else {
116 1.5531 : x = 1.0 / x; /* called 4/16 of the time. */
: }
: }
:}
这个输出结合了
gcov
和
gprof
的优点,每行代码都标注了样本计数和花费时间的百分比,且百分比是针对当前文件进行归一化的,总和为 100%。
多处理器性能
随着计算机技术的发展,多处理器系统越来越常见。对称多处理(SMP)是一种运行在多个 CPU 上的操作系统技术,已经存在多年。使用 SMP 可以提高繁忙服务器的整体系统吞吐量,因为调度器可以将进程分配到不同的 CPU 上。但对于单线程应用程序来说,在多处理器机器上运行速度并不会加快,因为它一次只能在一个处理器上运行。而多线程应用程序则能充分利用 SMP 的优势,通过将线程分配到不同处理器上实现更高的吞吐量。
近年来,微处理器厂商推出了多核和多线程 CPU,这使得许多桌面和笔记本电脑也需要支持 SMP 的操作系统来发挥这些新处理器的性能。下面我们将介绍几种常见的 SMP 硬件类型。
SMP 硬件类型
- 多处理器主板 :这是最古老的 SMP 实现方式,将两个或更多处理器安装在同一块主板上。这些处理器共享前端总线(FSB)、随机存取存储器(RAM)和北桥。但这种方式存在一些问题,例如由于多个设备共享总线,会影响 FSB 的速度,导致多处理器系统的 FSB 速度通常比单处理器系统慢。
而 AMD 的 Opteron CPU 则采用了不同的设计,它在 CPU 中集成了 DRAM 控制器,使每个 CPU 都能高速访问自己的 DRAM。同时,Opteron 用 Hypertransport 总线取代了 FSB 总线,这是一种基于数据包的点对点接口,解决了传统 FSB 的信号质量问题,允许在一块主板上安装更多的 CPU。不过,Hypertransport 总线速度在新标准发布前保持不变,而且集成的 DRAM 控制器可能无法及时跟上最新的 DRAM 技术。
- 对称多线程(SMT) :在 Linux 中,这是 Intel 的 Hyperthreading 技术的通用术语。具有 Hyperthreading 功能的处理器在操作系统看来就像多个核心的 CPU,但实际上它们不是真正的多核处理器,因为 SMT CPU 共享大部分片上资源,会相互竞争。
每个逻辑处理器都有自己的寄存器集和指令流水线,但它们共享片上缓存、内存管理单元(MMU)、转换后备缓冲区(TLB)和所有执行单元。因此,两个逻辑核心的处理速度不会比两个单核心处理器快两倍。
SMT 是 CISC 处理器特有的并行机会。由于 CISC 处理器的流水线更深,流水线停顿的影响比 RISC 处理器更严重,而提供多个逻辑 CPU 的流水线可以在流水线停顿时保持执行单元的忙碌。Hyperthreading(和 SMT)在加速多线程软件方面最为有效,因为线程共享内存和页表项,非常适合分布在逻辑 CPU 上,而且由于 SMT CPU 只有一个 MMU,线程比进程能获得更多的性能提升。
- 多核 CPU :CPU 厂商认为单 CPU 的时钟频率已经达到了极限,主要问题是高速处理器产生的热量过大。使用多核技术,芯片可以以较低的时钟频率运行,但在相同的时钟周期内执行更多的代码。例如,双核 CPU 可以以较慢的时钟运行,但仍然比以较高时钟频率运行的单核 CPU 更快。
综上所述,了解不同的性能分析工具和多处理器硬件类型,对于优化程序性能和设计适合多处理器环境的代码非常重要。在实际开发中,我们可以根据具体需求选择合适的工具和硬件架构,以提高程序的运行效率。
程序性能分析与多处理器性能优化全解析
CPU 并行性背景知识
为了更好地理解 SMP 和多核 CPU 的重要性,我们需要了解一些 CPU 并行性的背景知识。
RISC 与 CISC
在过去,人们不太信任编译器,程序员即使有编译器可用,也会选择用汇编语言编写代码,因为他们认为汇编语言是最有效的语言,并且很多汇编程序员不相信编译器能生成高效甚至正确的输出。
当时,大多数处理器是复杂指令集计算机(CISC),它们提供丰富的指令集和多种寻址模式,方便汇编语言编程。例如,摩托罗拉 68000 和英特尔 8086 就是典型的 CISC 处理器。摩托罗拉处理器用于原始的 Macintosh 计算机,而 8086 则用于 IBM PC(实际上是 8088,8086 的 8 位版本),其指令集至今仍是 IA32 架构的一个子集。
后来,IBM、斯坦福和伯克利的研究表明,通过创建更少的指令、有限的寻址模式和更低的指令延迟,可以更有效地利用 CPU 上的晶体管,这就是精简指令集计算机(RISC)处理器的诞生原因。一般来说,RISC 指令对编译器更友好,而不是对用户更友好。像 PowerPC、SPARC 和 MIPS 处理器就是 RISC 处理器的例子。
流水线和执行并行性
流水线技术是让 CISC 和 RISC 处理器运行更快的一项创新,它类似于亨利·福特在 20 世纪初用于加速汽车制造的装配线。在汽车装配中,将装配过程分解为多个步骤,每个工人专门负责一个步骤,从而提高了效率。
在 CPU 中,流水线将每条指令分解为独立执行的离散步骤,每个步骤都有专门的硬件(执行单元,有时也称为超标量单元)负责。然而,与装配线不同的是,不同的指令需要不同的阶段,有些指令执行时间更长。我们将指令通过流水线所需的时间称为指令延迟。
一个具有 20 级流水线的 CPU 可能需要多达 20 个时钟周期才能完成一条指令,但由于 CPU 可以同时处理 20 条指令,理论上的最大吞吐量可以达到每个时钟周期一条指令。但由于流水线停顿,这个理论最大值很少能实现。
流水线停顿通常是由条件分支指令引起的。当分支指令及其条件被解码时,流水线中可能已经有几条指令,这些指令可能会因为分支而被丢弃。此时,流水线需要重新填充,CPU 才能继续执行指令,这会导致吞吐量下降。流水线的深度决定了这种惩罚的程度,如下图所示:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([Full Pipeline]):::startend --> B(0: if (x)):::process
B --> C(1: Do this):::process
C --> D(2: Do that):::process
D --> E(3: Do other):::process
F([Stalled Pipeline]):::startend --> G(0: if (x)):::process
G --> H{x == false}:::decision
H -->|Yes| I(1: Empty!):::process
I --> J(2: Empty!):::process
J --> K(3: Empty!):::process
流水线停顿是不可避免的,因为编写代码时很难避免使用条件语句。许多 CPU 都有一些功能来尽量减少分支的影响,但效果取决于具体的应用程序。一般来说,流水线可以使处理器在短时间内达到峰值指令吞吐量,但在实际应用中,这种吞吐量很难持续。
RISC 处理器的一个常见特点是指令延迟较低,与 CISC 处理器相比,所需的指令流水线更短。这意味着 RISC 处理器在流水线停顿方面的影响相对较小。这也是为什么 IA32 处理器的时钟速度比类似的 RISC 处理器更快的原因之一,因为更深的流水线需要更快的时钟来保持延迟在纳秒级别较低,但为了提高时钟速度,有时又需要更深的流水线,这形成了一个恶性循环。
缓存
缓存是一个重要的特性,它使处理器能够比动态随机存取存储器(DRAM)允许的速度更快地运行。缓存允许核心以非常高的时钟频率运行,而这是目前的 DRAM 技术无法实现的。
由于缓存对性能至关重要,了解多核设计中缓存的分配方式对于预测应用程序的性能非常重要。在选择芯片时,多核设计的缓存架构可能是一个决定性因素。
不同 SMP 硬件的优缺点总结
| SMP 硬件类型 | 优点 | 缺点 |
|---|---|---|
| 多处理器主板 | 传统设计,技术成熟 | FSB 速度受限,信号质量问题;集成 DRAM 控制器可能跟不上最新技术 |
| 对称多线程(SMT) | 为 CISC 处理器提供并行机会,加速多线程软件 | 不是真正的多核,共享资源会产生竞争,处理速度提升有限 |
| 多核 CPU | 可以以较低时钟频率执行更多代码,减少热量产生 | - |
总结与建议
在程序性能分析方面,我们介绍了
gcov
、
gprof
和
OProfile
等工具。
gcov
可以统计代码行的调用次数,
gprof
能帮助确定程序花费时间的具体位置,而
OProfile
功能更强大,无需重新编译代码就能进行性能分析,并且能提供带注释的源代码,结合了
gcov
和
gprof
的优点。
在多处理器性能方面,我们了解了对称多处理(SMP)技术以及不同的 SMP 硬件类型,包括多处理器主板、对称多线程(SMT)和多核 CPU。不同的硬件类型有各自的优缺点,在设计代码时,我们需要根据具体的应用场景和硬件环境进行选择。
为了优化程序性能,我们建议:
1. 在开发过程中,定期使用性能分析工具来找出性能瓶颈,根据分析结果进行针对性的优化。
2. 对于多线程应用程序,充分利用 SMP 技术,将线程合理分配到不同的处理器上,以提高吞吐量。
3. 在选择硬件时,考虑应用程序的特点和需求,选择合适的 SMP 硬件类型,例如,如果是多线程应用,多核 CPU 或支持 SMT 的处理器可能是更好的选择。
通过深入了解这些性能分析工具和多处理器硬件知识,我们可以更好地优化程序性能,提高系统的运行效率。
超级会员免费看

被折叠的 条评论
为什么被折叠?



