性能分析工具GpProfile
作者 陈省
两年前在我的机器还是AMD K6 233和128M内存的时候,我曾经尝试用JBuilder和JDK 1.2编译运行过的最复杂的Java程序是Hello World,当时Java程序给的印象就是每次编译运行的间歇我可以煮一杯咖啡,这使得我在当时非常羡慕那些工作悠闲成天喝咖啡的Java程序员。
在将我的古董电脑升级为AMD Athlon XP 1700+和256M内存之后,有一天我忍不住又打开了JBuilder。这次我吃惊地发现屏幕不再疯狂闪动得令我淌眼泪,硬盘不再滋滋乱响得让我心痛——原来JBuilder也可以和Delphi运行的一样顺畅。打那以后,我就从虚拟机的批判者成为了垃圾回收的狂热信徒了。
由此可见,商业软件要想成功,性能无疑是非常重要的,你的程序占用资源越少,速度运行越快,你就有可能占有更多的市场份额。因此一个好的性能分析工具无疑是必不可少的。下面我将介绍的GpProfile是由Primoz Gabrijelcic开发的开放源码的性能分析工具,它可以分析代码的运行速度,帮你定位系统性能的瓶颈。GpProfile目前最新的版本是1.3.3,在本书的配套光盘上有该软件的安装文件和源代码。
GpProfile 的特点包括
开放了全部源代码,可以方便地进行修改
除了可执行文件外,还可以分析DLL和包文件
支持多线程
支持条件编译
集成的代码预览
集成的性能分析结果察看工具
使用GpProfile分析代码性能
学习新事物的最好的方法就是动手去实践。Delphi在Demos\Threads目录下提供了一个例子来演示如何使用线程,在该例子中比较了三种不同的排序方法(起泡排序,选择排序和快速排序)的性能差异。我们将在这个例子的基础上演示如何使用GpProfile来定量地比较不同算法的性能差异。
下面是我修改了Borland的例子之后的代码,主要的改变是去掉了线程和排序可视化显示的部分:
{-----------------------------------------------------------------------------
Unit Name: CProfile
Author: hubdog(陈省)
Email: hubdog@263.net
Purpose: 演示如何使用GpProfile来分析程序运行的性能
History:
2003-4-4 创建本单元
-----------------------------------------------------------------------------}
unit CProfile;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure BubbleSort(var A: array of Integer);
procedure SelectionSort(var A: array of Integer);
procedure QuickSort(var A: array of Integer);
procedure RandomizeArrays;
end;
type
TSortArray = array[0..15000] of Integer;
var
Form1: TForm1;
BubbleSortArray, SelectionSortArray, QuickSortArray: TSortArray;
implementation
{$R *.dfm}
//起泡排序
procedure TForm1.BubbleSort(var A: array of Integer);
var
I, J, T: Integer;
begin
for I := High(A) downto Low(A) do
for J := Low(A) to High(A) - 1 do
if A[J] > A[J + 1] then
begin
T := A[J];
A[J] := A[J + 1];
A[J + 1] := T;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
Screen.Cursor := crHourGlass;
try
//调用三次,以便观察算法的平均效率
for I := 0 to 2 do
begin
//初始化数组
RandomizeArrays;
//执行三个排序
BubbleSort(BubbleSortArray);
SelectionSort(SelectionSortArray);
QuickSort(QuickSortArray);
end;
finally
Screen.Cursor := crDefault;
end;
end;
//快速排序
procedure TForm1.QuickSort(var A: array of Integer);
procedure QuickSort(var A: array of Integer; iLo, iHi: Integer);
var
Lo, Hi, Mid, T: Integer;
begin
Lo := iLo;
Hi := iHi;
Mid := A[(Lo + Hi) div 2];
repeat
while A[Lo] < Mid do
Inc(Lo);
while A[Hi] > Mid do
Dec(Hi);
if Lo <= Hi then
begin
T := A[Lo];
A[Lo] := A[Hi];
A[Hi] := T;
Inc(Lo);
Dec(Hi);
end;
until Lo > Hi;
if Hi > iLo then QuickSort(A, iLo, Hi);
if Lo < iHi then QuickSort(A, Lo, iHi);
end;
begin
QuickSort(A, Low(A), High(A));
end;
//初始化用于排序的数组
procedure TForm1.RandomizeArrays;
var
I: Integer;
begin
Randomize;
for I := Low(BubbleSortArray) to High(BubbleSortArray) do
BubbleSortArray[I] := Random(170);
SelectionSortArray := BubbleSortArray;
QuickSortArray := BubbleSortArray;
end;
//选择排序
procedure TForm1.SelectionSort(var A: array of Integer);
var
I, J, T: Integer;
begin
for I := Low(A) to High(A) - 1 do
for J := High(A) downto I + 1 do
if A[I] > A[J] then
begin
T := A[I];
A[I] := A[J];
A[J] := T;
end;
end;
end.
上面代码中RandomizeArrays方法是用来初始化排序的数组的,而BubbleSort, SelectionSort和QuickSort方法分别对应于起泡排序,选择排序和快速排序过程。注意在Button1Click事件中,我们反复进行了三次排序,以便观察排序算法的排序稳定性和平均效率。
使用GpProfile添加性能分析代码
GpProfile安装程序会在Delphi的IDE的Tools菜单下添加两个菜单项:
GpProfile
GpProfile –remove instrumentation
点击GpProfile菜单将启动GpProfile后,GpProfile会自动加载当前IDE中的项目文件作为测试项目,示意图如下:
在GpProfile 的主窗体中, 有Instrumentation 页,在该页中,我们可以看到测试项目中的全部的函数和类的全部过程和函数。选中某个方法后,会在下面的编辑框内显示相应的代码的预览,选中方法前面的CheckBox则表示GpProfile会分析该函数或过程的性能执行情况。
完成选择之后,点击菜单Project | Instrument,GpProfile会修改工程的源代码,添加GpProfile Api的声明,这时我们在Delphi IDE 中会收到一个源代码变更,要求重新载入源程序的消息。选“ 确定”后,就会看到修改后的代码了,代码示意如下:
procedure TForm1.SelectionSort(var A: array of Integer);
var
I, J, T: Integer;
begin {>>GpProfile} ProfilerEnterProc(3);
try {GpProfile>>}
for I := Low(A) to High(A) - 1 do
for J := High(A) downto I + 1 do
if A[I] > A[J] then
begin
T := A[I];
A[I] := A[J];
A[J] := T;
end;
{>>GpProfile} finally ProfilerExitProc(3);
end; {GpProfile>>}
end;
其中被包围在{>>GpProfile}{GpProfile>>}和{>>GpProfile U}{GpProfile U>>}等标记之间的代码就是GpProfile的性能分析代码。
接下来就要编译运行修改后的代码了,注意要想编译添加了GpProfile Api调用的程序,我们要将GpProfile的安装路径添加到Library Path中,见下图示意:
否则编译时,会提示无法找到GpProf.dcu。添加了路径后,点击运行就可以了。运行程序直到应用程序结束,GpProfile Api会自动在应用程序所在目录生成*.prf 文件,这是分析性能的记录文件。
查看测试报告
如果在运行应用程序时, GpProfile打开着。那么,应用程序运行结束时,GpProfile 会自动打开测试报告。否则,你应该在GpProfile的主界面中,选择Profile\Open 菜单,到应用程序所在目录中去打开测试报告。
打开测试报告后,我们在主界面中切换到Analysis页就可以看到性能报告了,示意图如下:
报告分为下面几列:
Procedure:显示我们选定的要分析的方法名称
%Time:显示各个方法运行时间相对于总的运行时间的百分比
Time: 实际的运行时间的秒数
Calls:表示被分析的方法运行的次数。(有时一次运行给出的性能数据可能不稳定,可以多次运行取结果平均值)
Min/Call:多次调用时,所用的最少时间
Max/Call:多次调用时,所用的最多的时间
Avg/Call:多次调用时,每次的平均时间
从图中可以看出,三种排序法中快速排序的效果是最好的,对于15000个元素的数组,速度是起泡排序的745倍,是选择排序的253倍。想像一下,如果在你写的Delphi程序中有30个过程的算法是类似于起泡排序这样缓慢的算法的话,我敢打赌使用了30个类似于快速排序的算法的Java程序一定比你的Delphi程序快20倍。估计那时你的老板一定会让你转行去煮咖啡了。
言归正传,从上面的操作来看,GpProfile使用起来非常简单,应该是我用过的性能分析器中使用最简单的一个。不过它在我们的代码中加入了很多自定义的标记和Api调用,如果要手工地删除,实在是很不方便,幸好点击Project | Remove Instrumentation就可以将代码中插入的{>>GpProfile}等标记自动删除了,不过为了保险起见,我建议你还是在Remove Instrumentation之前备份一下代码,以免造成损失。
总结
GpProfile是一个简单方便的性能分析工具,而且是免费开源的。但是它也有局限性,那就是作为一个源代码性能分析工具,它不能对没有源代码的项目进行分析,幸好一般来说这不是问题。此外,GpProfile只能告诉我们代码有多烂,但是它不会教你如何改进算法。