40、程序性能分析与多处理器性能优化全解析

程序性能分析与多处理器性能优化全解析

在程序开发过程中,性能优化是一个至关重要的环节。了解程序的运行性能,找出性能瓶颈,是提升程序效率的关键。下面我们将介绍几种常用的性能分析工具,并探讨多处理器系统的性能特点和相关硬件类型。

性能分析工具

在分析程序性能时,我们常常需要借助一些工具。例如,当代码中没有详细的注释来帮助我们了解程序的运行情况时,就可以使用像 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
  1. (可选)重置统计信息:
$ opcontrol --reset
  1. 运行要分析的应用程序:
$ ./profme
  1. 导出统计信息并停止守护进程(可选):
$ opcontrol --dump
  1. 使用报告工具查看结果:
$ 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 的处理器可能是更好的选择。

通过深入了解这些性能分析工具和多处理器硬件知识,我们可以更好地优化程序性能,提高系统的运行效率。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值