性能分析

在軟體工程中，**效能分析**（**performance analysis** 也稱為 **profiling**），是以收集程式執行時資訊為手段研究程式行為的分析方法，是一種動態程式分析的方法。

效能分析量測像是程式的空間或時間複雜度、特定指令的使用情形、函式呼叫的頻率及執行時間等。效能分析的目的在於決定程式的哪個部分應該被最佳化，從而提高程式的速度或者記憶體使用效率。

效能分析可以由程式的原始碼或是可執行檔進行。一般會使用稱為效能分析工具（profiler）的工具進行。效能分析工具會使用許多不同的技術，可能是以事件為基礎（Event-based）的、統計的、指令導向的、仿真的方法。效能分析工具常用在效能工程的過程中。

## 效能分析工具

> 
> "若要了解程式行為，程式分析工具非常重要。電腦架構分析師需要這類工具來評估程式在新的系統結構中運作的情形。軟體撰寫者需要這類工具來分析程式，並分析出其中關鍵的區塊。編譯器撰寫者需要這類工具來評估其指令排程或分支預測演算法運作的情形" _-- (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 及 OLIVER 工具。

## 相關條目

* 演算法效率
* 靜態程式分析
* 評效基準
* 效能分析工具列表
* 效能工程
* 效能預測
* PAPI 是一套針對現代微處理器的硬體效能計數器的可攜式介面（以函式庫的方式提供）
* 效能調校
* 最壞條件執行時間（WCET）
* Java 效能
* 軟體考古學

## 參考資料

## 外部連結

* 演示 "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% 的程式所造成