性能分析

在软件工程中，性能分析（performance analysis，也称为profiling），是以收集程序运行时信息为手段研究程序行为的分析方法，是一种动态程式分析的方法。

性能分析量测像是程式的空间或时间复杂度、指令集仿真器、函式呼叫的频率及执行时间等。性能分析的目的在于决定程序的哪个部分应该被程式最佳化，从而提高程序的速度或者内存使用效率。

性能分析可以由程式的源代码或是可执行档进行。一般会使用称为性能分析工具（profiler）的工具进行。性能分析工具会使用许多不同的技术，可能是以事件为基础（Event-based）的、统计的、指令导向的、仿真的方法。性能分析工具常用在性能工程的过程中。

## 性能分析工具

<image>

"若要了解程式行为，程式分析工具非常重要。电脑架构分析师需要这类工具来评估程式在新的系统结构中运作的情形。软件撰写者需要这类工具来分析程式，并分析出其中关键的区块。编译器撰写者需要这类工具来评估其指令排程或分支预测算法运作的情形" -- (ATOM, PLDI, '94)

性能分析工具使用广泛的技术手段收集数据，包括硬件中断、指令 (计算机科学)、作业系统hooking、CPU内置的性能计数寄存器，等等。

性能分析输出会是：

- 观察到的事件的统计摘要（概要文件）

摘要配置文件信息通常会根据事件发生的源代码语句进行注释显示，因此测量数据的大小与程序的代码大小成线性关系。

```
/* ------------ 源代碼------------------------- 發行次數 */            
0001             IF X = "A"                     0055
0002                THEN DO                      
0003                  ADD 1 to XCOUNT           0032
0004                ELSE
0005             IF X = "B"                     0055
```

- 所有事件的记录流（亦称踪迹，英文“trace”）

事件的记录流则与指令路径长度成线性关系，也就是和运行时长成线性关系。由于资料量过大，有时记录会不切实际。所以，有些程式分析工具可以设定在某特殊条件下才启动事件踪迹的记录，在另外特殊条件下结束事件踪迹的记录。

对于顺序执行的程序，通常轮廓就足够了。但并行计算程序的性能问题（等待消息或者同步问题）和事件的时间顺序有关，因此需要全部的踪迹才能找到问题。

- 用hypervisor持续性的互动监控（针对事件连续性或周期性显示在萤幕上）

在观看在执行程式的相关度量时．可在任意时刻启动或结束事件踪迹的记录，也可以在一些关键的点上暂停异步的程式来看和其他平行处理程式之间的互动关系。

## 历史

早在1970年代，IBM System/360及IBM System/370的平台就有性能分析工具，一般是用计时器中断在固定的时间纪录程式状态字（PSW）来侦测程式执行时的“过热点”（hot spots）。这是早期使用抽样方式进行性能分析的范例之一。在1974年时，指令集仿真器就允许完整的事件踪迹，以及其他性能监控的机能。

以性能分析工具为主的UNIX程式分析至少可以回溯到1979年，当时Unix系统有一个基础工具prof，可以列出每一个函式，也列出此函式总共花了多少时间。1982年时gprof工具延伸此概念，可以列出完整的函式调用图。

1994年时，迪吉多的Amitabh Srivastava和Alan Eustace提出了描述ATOM的论文。ATOM是一个平台，可以将程式配合其性能分析工具调整，在编译期间，ATOM会在要分析的程式中加入程式码，而加入的程式码会输出分析资料，这种修改程式，输出自身份析资料的技术，称为逻辑注入。

2004年时，gprof和ATOM论文都出现在前50个最具影响力的程式语言设计和实现会议（PLDI）论文中。

## 以输出方式分类

### 一般性能分析器

一般性能分析器（flat profiler）根据函式呼叫计算平均的函式呼叫次数，而且不会根据被呼叫函数或是执行脉络（context）细分函式呼叫次数。

### 函式调用图性能分析器

函式调用图性能分析器（call graph profiler）会显示函数被呼叫的次数及频率，也会列出函式调用链（call-chains），有些软件会列完整的调用链，有些不会。

### 输入敏感性能分析器

输入敏感性能分析器（input-sensitive profiler）将性能度量与输入工作负载特征（例如输入大小或输入值）相关联，相比于一般性能分析器或调用图分析器增加了一个维度。它会生成图表描述应用程序性能随其输入的变化情况。

## 以分析方式的分类

性能分析器本身也是程式，可以在被分析程式执行时收集相关资讯，来分析该程式。根据收集到资讯的细微度，以及收集资讯的方式，可以分为事件为基础的性能分析器，或是统计式的性能分析器。有些性能分析器为了收集资讯，会中断程式的执行，因此在时间量测上有一定的分辨率限制。

### 事件为基础的性能分析器

以下列出的程式语言有事件为基础的性能分析器：

- Java：JVMTI（JVM工具界面）API，以前称为JVMPI（JVM性能分析界面），提供给性能分析器的hook，可以抓到像函式呼叫、类别载入、卸载、线程的进入及离开等事件。
- .NET框架：利用性能分析的API，可以连接到像是COM服务器的性能分析代理器（profiling agent）。像Java一様，在执行会提供许多回调函式给代理器，可以捕捉到像是方法JIT/进入/离开，物件创建及其他。特别的是性能分析代理器可以用任意方式改写目的应用程序的字节码。
- Python：Python的性能分析包括profile模组，以调用函式图为基础的hotshot，以及用'sys.setprofile'函式来捕捉像c_{call,return,exception}及python_{call,return,exception}的事件。
- Ruby：Ruby也用类似Python的性能分析界面。目前有在profile.rb中的一般效能分析器及相关模组。

### 统计式的性能分析器

有些性能分析器是用取样的方式运作。取様式的性能分析器利用操作系统的中断，在固定时间取様目的程式的调用栈。取様式的性能分析器在数值上较不精准，但对目的程式执行时间的影响最小，允许目的程式可以在接近全速的速度下运作。

所得到的资料不是精准值，只是统计上的近似值而已。“实际误差的量一般会大于一个取样时间。若某一数值是取様时间的n倍，其误差约为n倍取样时间的平方根。”

在实务上统计式的性能分析器会比其他的分析方式更能知道目的程式各部分占的比例，而且相较之下有较少的边际效应（例如内存快取或是指令解码的管道线等），由于统计式的性能分析器对程式执行速度的影响较小。因此可以侦测到一些其他方式侦测不到的问题。这种方式可以看出使用者模式及可中断系统模式（例如系统呼叫）分别占的时间。

不过由于系统程式需处理中断，仍然会花一些CPU的执行周期，分散快取的读取，而且无法分辨在不可中断核心模式下的行为。

有些特制的硬件可以克服这类的问题：有些最近MIPS微理器中，JTAG界面有一个PCSAMPLE暂存器，可以用一种无法侦测到的方式来取様程式计数器。

最常用的统计式的性能分析器包括AMD的CodeAnalyst、苹果公司的Shark（OSX）、oprofile（Linux）、Intel的VTune及Parallel Amplifier（Intel Parallel Studio的一部分）。

### 插装型的性能分析器

有些性能分析可以用插装（也称为逻辑注入）的方式处理目的程式，也就是在目的程式中加入额外指令来收集需要的资讯。

程式的插装会影响程式的效能，可能会出现不精确的结果及 heisenbug（捉摸不定，不易重现的bug）。插装一定会对程式执行有些影响，常见的情形是使程式变慢。不过插装可以特定只针对部分程式，而且可以小心控制以使影响降到最低。其对于特定程式的影响是看插装放置的位置，以及捕捉踪迹（trace）的机制。有些处理器有硬件支持可以捕捉踪迹，插装可以只占一个机器语言周期的时间。一般可以从结果中移除插装的影响。

gprof是一个同时用插装及取様的性能分析器的例子。插装用来取得被呼叫函式的资讯，而实际花的时间则是由取様方式来获得。

插装是决定性能分析器可控制程度及时间分辨率的关键。以下是一些方式的分类。

- 手动：是由程式设计者加入指令，在执行时计算相关资讯，例如计算事件或是呼叫像是应用程序响应测试（ARM）标准的API。
- 源代码层级自动处理：依照插装政策，利用自动化工具自动在源代码中加入instrumentation，像Parasoft公司的Insure++。
- 中间语言：在组合语言或是bytecode中加入针对多种高阶语言的instrumentation，例如OpenPATOpenPAT。
- 编译器协助：像gprof和Quantify都是这类的例子，像用gcc -pg ...可以使用gprof，用quantify g++ ...可以使用Quantify。
- 二进制翻译：此工具在编译好的可执行档中加入instrumentation，例如ATOM。
- 执行时插装：代码直接在执行前修改，工具可以完成的监控及控制程式的执行，例如用Pin、Valgrind、DynamoRIO。
- 执行时注入：修改程度比执行时插装要小，代码在执行时修改，令加入跳跃到协助用函式的指令，例如和DynInst。

### 直译器式的插装

- 直译器式除错选项，可以在直译器处理每个目的指令时，收集性能量度的相关资讯。像字节码、控制表及JIT的直译器都在目的码执行时有完整的控制能力，因此有机会收集到非常全面的资料。

### hypervisor/模拟器(simulator)

- Hypervisor：在hypervisor下执行（一般而言）没有修改的程式，可以收集相关资讯，例如SIMMON工具。
- 模拟器及hypervisor：在指令集模拟器执行（一般而言）没有修改的程式，可以互动式及选择性的收集相关资讯，例如SIMON及IBM OLIVER工具。

## 外部链接

- 演示" Using VSTS Performance Tools to Speed Up Your App "，演示者是微软的程序员Ian Huff，演示内容是Visual Studio Team System 2005中的性能分析器
- 文章" Need for speed -- Eliminating performance bottlenecks "，是有关利用IBM的Rational Application Developer进行Java应用程序的执行时间分析
- 体验报告" Execution time optimisation of an Avionics system"，其中发行现Avionics system中最坏执行时间中的30%是由只占1.2%的程式所造成