DELPHI技术

博客园 首页 新随笔 联系 订阅 管理

性能分析工具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只能告诉我们代码有多烂,但是它不会教你如何改进算法。

posted on 2005-07-11 08:19  DELPHI技术  阅读(1567)  评论(0编辑  收藏  举报