Win32 FPC/Delphi/BCC/MinGW/VC编译器性能对比测试
(Matrix Multiplication)
0 序
本文的观点与结论仅仅包括作者本人在特定软件、硬件环境下的真实试验过程得到。本文仅仅提供一种客观的参考建议,不含有任何涉及褒贬的叙述目的,不含有任何绝对正确的担保责任,不希望引起更多的争论。除标注引用部分之外,均为原创文献。
1 引言
长久以来,我需要同时使用多种编程语言和多种开发环境。从最早1995年的Delphi 1.0开始,这个运行在当时的Windows 3.1系统之上的RAD软件使我成为Delphi和Object Pascal语言的忠实Fans;其后由于工作的原因转而使用C/C++编程,熟悉了Win32平台之上的一些C/C++开发环境与编译器;近两年(抱歉,我发现新事物的速度是如此之慢)发现了FreePascal,一个相当不错的跨平台Pascal编译器,支持多种Pascal方言(当然包括Delphi语法),尤其是随着FreePascal 2.0的发布,渐渐使其具备了生产环境的能力。
我主要的工作领域在基础级的服务器软件设计,所以Java和C#等基于VM的语言由于运行效率和交互环境限制的原因没有进入过我的视线,故我对其了解甚少。
在Win32系统下,我们可以接触到的不同的C/Pascal编译器,有免费开源的,也有昂贵的商业产品,为了安慰开发者有时手足无措的选择(当然,绑死在一个固定产品的开发者显然不需要顾虑这些)和满足开发者的好奇关注,网上出现了专门针对编程语言和编译器性能的对比测试,论坛上为了同样的论题也引发了大量的争论。从“The Great Win32 Computer Language Shootout”(http://dada.perl.it/shootout/)站点,我也得到了相应的信息,但是直觉告诉我,这些数据不一定是准确的,因为性能数据的年限显得有些旧,MinGW32、VC、FreePascal的用于测试的编译器版本很旧,而且这些产品在后续的版本都出现了大幅度的升级,如FreePascal 1.04升级到了2.0.2,MinGW从2.95升级到今日的3.4.x,VC6也升级到了VC8,原有的数据已经不能说明当今的情况。
在最近的一段时间里,我需要评估FreePascal的能力。FreePascal(http://www.freepascal.org)已经被移植到几乎所有的主流平台之上,加之与Object Pascal语法和RTL的优秀兼容性,完全符合一次编写,到处编译运行的特征,以及具备在跨平台的服务器端系统开发中接近准入的能力。
2 环境
那么,我尝试重现其中的部分测试过程,来对最新的编译器技术作以测试。测试的编译器环境包括FPC(FreePascal)/Delphi/BCC/MinGW/VC,它们各自的版本信息如下:
--fpc
Free Pascal Compiler version 2.0.2 [2005/11/26] for i386
Copyright (c) 1993-2005 by Florian Klaempfl
--dcc32(Delphi7)
Borland Delphi Version 15.0
Copyright (c) 1983,2002 Borland Software Corporation
--bcc32
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
--gcc
gcc (GCC) 3.4.2 (mingw-special)
Copyright (C) 2004 Free Software Foundation, Inc.
--cl(VC2005)
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
它们中间包括2个Pascal编译器fpc,dcc32和3个C编译器bcc32,gcc,cl,并且fpc,bcc32和gcc-mingw都是免费自由下载使用的软件,可以从Internet之上直接获得。
虽然是完成相同的计算,得到相同的测试结果,但是Pascal语言和C语言之间的语法差异使得它们之间相互的对比似乎有失公平,是的,不过可以忽略不计。
硬件的测试环境为ThinkPad X31 267239C (PM 1.5G, 768M, 80G. Windows XP SP2),并且关闭所有运行中的应用程序。
3 方法
通过编写一个独立的计时工具软件来完成对之前编译结果的测试工作,我简单的写了一个小工具(timer),它很简陋,但是确实可靠,并且精度足够。
timer工具用于测量一个控制台应用程序的运行时间,待测试的控制台程序是由timer工具启动,并且采样了从创建待测试进程(CreateProcess)直到待测试进程运行完毕(WaitForSingleObject(hProcess, INFINITE)跳出)的时间间隔,其中包括了创建进程时间和待测试程序的启动代码与退出代码时间。
为了降低测试的误差,timer工具将待测试进程的优先级类别提升为HIGH_PRIORITY_CLASS,以防止当前大部分运行中进程的干扰。另外,timer工具会首先不计时运行一次待测试程序,使得后续测试都有Cache载入的效应,再连续计时运行待测试程序5次,求得5次耗时的平均值作为最终成绩,尽可能减少误差的影响。
timer工具的使用方法为: timer <arguments> ...,后续的参数被当作新的命令行创建被计时受控的进程,如timer program.exe xx yy zz等等,很简单,不是吗?不过需要注意一点,待测的程序必须是控制台程序,而且不能有运行中来自键盘的输入(测试性能时怎么可能会有呢?)。在计时运行过程中,屏蔽了创建进程的StdIn和StdOut以消除潜在额外的负荷。
timer工具的完整源代码如下 (timer.cpp),Eclipse/CDT 3.1+MinGW 3.4.2测试通过。
/* timer.cpp: (c) yaoyong 2006, eclipse/cdt, mingw32 */
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <numeric>
#include <windows.h>
usingnamespace std;
int64_t perf_freq, perf_c_start, perf_c_stop;
string cmd_line;
inlinevoid timer_start(void)
{
QueryPerformanceCounter((_LARGE_INTEGER *)&perf_c_start);
}
inlineint timer_stop(void)
{
QueryPerformanceCounter((_LARGE_INTEGER *)&perf_c_stop);
QueryPerformanceFrequency((_LARGE_INTEGER *)&perf_freq);
return (perf_c_stop - perf_c_start) * 1000 / perf_freq;
}
void error_exit(constchar *err_msg)
{
cout << err_msg << endl;
exit(-1);
}
void os_error_exit(void)
{
DWORD err_code = GetLastError();
staticchar err_msg[256];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err_code, 0, err_msg,
sizeof(err_msg), NULL);
error_exit(err_msg);
}
void perform_execution(bool allow_stdio)
{
STARTUPINFOA startup_info;
memset(&startup_info, 0, sizeof(startup_info));
startup_info.cb = sizeof(startup_info);
if(!allow_stdio)
{
startup_info.dwFlags = STARTF_USESTDHANDLES;
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
startup_info.hStdInput = INVALID_HANDLE_VALUE;
startup_info.hStdOutput = INVALID_HANDLE_VALUE;
}
PROCESS_INFORMATION process_info;
memset(&process_info, 0, sizeof(process_info));
if(CreateProcessA(NULL, (char *)cmd_line.c_str(), NULL, NULL, FALSE,
HIGH_PRIORITY_CLASS, NULL, NULL, &startup_info, &process_info))
{
WaitForSingleObject(process_info.hProcess, INFINITE);
CloseHandle(process_info.hThread);
CloseHandle(process_info.hProcess);
}
else
os_error_exit();
}
void parse_cmd_line(int argc, char *argv[])
{
for(int i = 1; i < argc; i++)
cmd_line.append(argv[i]).append(" ");
cout << "Execute: " << cmd_line << endl;
}
void run_preload(void)
{
constint PRELOAD_COUNT = 1;
for(int i = 0; i < PRELOAD_COUNT; i++)
perform_execution(i == 0);
}
void run_timer_test(void)
{
constint TIMER_TEST_COUNT = 5;
vector<int> time_list;
for(int i = 0; i < TIMER_TEST_COUNT; i++)
{
timer_start();
perform_execution(false); // disable stdin/stdout
int time_once = timer_stop();
time_list.push_back(time_once);
cout << "Cycle: " << i + 1 << ", execution time: " << time_once << "ms."
<< endl;
}
int avg_time = accumulate(time_list.begin(), time_list.end(), 0)
/ time_list.size();
cout << "Average time: " << avg_time << "ms." << endl;
}
int main(int argc, char *argv[])
{
parse_cmd_line(argc, argv);
run_preload();
run_timer_test();
return 0;
}
4 测试
根据之前的步骤,待测试的程序组如下:
|
编译器
|
文件名
|
编译选项
|
|
bcc32
|
matrix.bcc.exe
|
|
|
bcc32
|
matrix.bcc.o2.exe
|
-O2
|
|
dcc32
|
matrix.delphi.exe
|
-CC
|
|
fpc
|
matrix.fpascal.exe
|
-Mdelphi
|
|
fpc
|
matrix.fpascal.o2.or.exe
|
-Mdelphi -O2 –Or
|
|
fpc
|
matrix.fpascal.o3.or.exe
|
-Mdelphi -O3 –Or
|
|
fpc
|
matrix.fpascal.o3.or.ou.exe
|
-Mdelphi -O3 –Or –Ou
|
|
fpc
|
matrix.fpascal.release.exe
|
-Mdelphi -dRELEASE
|
|
gcc
|
matrix.mingw.exe
|
|
|
gcc
|
matrix.mingw.o2.exe
|
-O2
|
|
gcc
|
matrix.mingw.o3.exe
|
-O3
|
|
cl
|
matrix.vc.exe
|
|
|
cl
|
matrix.vc.o2.exe
|
-O2
|
|
cl
|
matrix.vc.ox.exe
|
-Ox
|
所有的测试比较在bcc32/dcc32/fpc/gcc/cl之间展开,可以看得出对于很多编译器而言,表中所列的编译选项参数空间并不“充裕”,只使用了通用的优化选项(-Ox),对于特定的、更激进的优化选项限于我的了解范围并未使用,兴趣的开发者可以继续探索可以改进的参数空间,以求得更加“超杀”的能力。
需要说明的情况:
1、Delphi7编译器dcc32默认状态下优化模式是开启($O+)的,因此dcc32使用默认的编译选项已经包含了优化。-CC的选项为指定程序为Console模式(等价于{$APPTYPE CONSOLE})。
2、FreePascal编译器fpc为本次的重点考察对象,因为相对于其他编译器,fpc是新来者,希望获得其实际的能力。fpc采用Delphi语法模式编译(-Mdelphi)以求得最相似的可比性,其-Or编译选项打开寄存器变量优化,-Ou编译选项打开非确定性优化,这也是重点的考察选项。另外fpc预置了DEBUG和RELEASE两种编译方案,其RELEASE方案的参数为“-OG2p3 –Xs”。
for %a in (matrix.*.exe) do timer %a 300 >> result.log
测试结果输出保存在result.log文件中。
测试结果明细表(N=300,时间均以ms为单位,来自result.log):
|
待测程序(.exe)
|
Cycle1
|
Cycle2
|
Cycle3
|
Cycle4
|
Cycle5
|
平均时间
|
|
matrix.bcc
|
53
|
51
|
51
|
51
|
51
|
51
|
|
matrix.bcc.o2
|
45
|
45
|
45
|
45
|
45
|
45
|
|
matrix.delphi
|
29
|
29
|
29
|
29
|
29
|
29
|
|
matrix.fpascal
|
82
|
85
|
81
|
81
|
81
|
82
|
|
matrix.fpascal.o2.or
|
58
|
58
|
57
|
57
|
58
|
57
|
|
matrix.fpascal.o3.or
|
58
|
58
|
57
|
58
|
57
|
57
|
|
matrix.fpascal.o3.or.ou
|
57
|
58
|
58
|
57
|
57
|
57
|
|
matrix.fpascal.release
|
76
|
76
|
75
|
76
|
76
|
75
|
|
matrix.mingw
|
96
|
93
|
92
|
93
|
94
|
93
|
|
matrix.mingw.o2
|
32
|
32
|
33
|
31
|
32
|
32
|
|
matrix.mingw.o3
|
27
|
26
|
28
|
26
|
27
|
26
|
|
matrix.vc
|
91
|
95
|
91
|
92
|
92
|
92
|
|
matrix.vc.o2
|
22
|
22
|
22
|
23
|
23
|
22
|
|
matrix.vc.ox
|
22
|
23
|
22
|
22
|
22
|
22
|
5 结论
根据上面的图表,可以得到很多感兴趣的有用信息,但是存在如下前提:
1、测试没有包含全部“可用”的编译器,可能存在测试之外的性能优良的编译器。
2、测试的各个编译器专属的优化选项并没有完全列出。
3、测试局限于Matrix Multiplication项目,检验编译器基本整数运算的能力,可以潜在检验编译器的部分优化能力,但是不能引申为综合应用的性能。
4、测试过程依据并参照“The Great Win32 Computer Language Shootout”项目。
满足前提之后,得到的结论如下:
1、VC2005的编译器(cl)具备最佳的优化后性能,大幅度领先其它的产品,而且在-O2优化选项时已达到相当最佳化,更为激进的-Ox的效果相当有限,由此可得Microsoft的C/C++编译器优化技术相当先进。我以前也使用过VC2003,对它的性能印象深刻,只是没有数据来验证判断。cl编译器未加优化选项的性能很明显地接近垫底,“忠实”地反映了编译选项。值得关注的是cl编译器的优化后的性能超过了未优化的4倍多,再提示一点,Debug编译配置下默认是关闭任何优化的。还有一点,VC2005 Express保持了同样的性能,而且,它还是免费的。
2、MinGW (gcc 3.4.2) 是相当引人注目的,有点出乎我的意料,颠覆了我想象的gcc优化性能和移植后的性能损失。长久以来,我对于MinGW性能一直没有清楚的感觉,但是在这局部的测试中,带有-O3级别优化选项的MinGW (gcc) 性能紧紧排在VC2005的身后,而且差距是相当微小的。gcc 3.4.2生成和优化Win32代码的能力的确优秀,大多数文献中说到的gcc重于移植性次于性能的观点已经过时了,那些文献描述的大致是gcc 2.x.x系列吧。有一点需要值得注意的地方是,-O3与-O2选项出现了明显的性能落差,超过了20%!巨大的差异使得MinGW (gcc) 采用-O2选项编译的程序无法发挥最佳化的能力。可是,天哪,很多Open Source项目的makefile文件里却写着-O2,如BerkeleyDB等,需要自行修正一下。相信等待gcc 4.x进入成熟期,移植到MinGW中会更加优秀。
3、Borland Delphi 7 (dcc32 Version 15.0) 也不辜负期望,冲到了第一阵营中。尽管Delphi 7发布的时间在“遥远”的2002年,而且其编译器优化技术进步缓慢,但是之前良好的技术基础使其没有落败。从数据可以得出,卸掉VCL的重型框架,Object Pascal语言还是很轻巧的。dcc32编译器编译速度飞快,可用的编译选项非常少,与速度优化相关的主要有$O,而且默认状态下还是开启的。最终的测试性能落后于VC2005和MinGW (gcc) 的最佳化结果,介于MinGW (gcc) -O2与-O3之间,性能还是相当不错的。除去GUI部分的VCL以外,单单依靠Object Pascal语言也是可以完全胜任复杂的Win32服务端逻辑的开发。目前Delphi已经发布了Delphi 2006,不过价格还是很昂贵,没有用过。2006年的2月,突然得到Borland意图出售IDE部门的消息,作为一个从Turbo Pascal 5开始的使用者,哎,没什么说的啦。
4、Borland C++ Compiler 5.5.1 (bcc32) 这是Borland放在网上免费下载的C/C++ Compiler,2000年发布的产品,目前有多少人在用不得而知。bcc32在我这里的唯一用途是编译简单的C/C++文件至obj,静态链接进Delphi程序中使用。测试中bcc32似乎是“饮酒过量”,表现只能用“诡异”来形容。最高优化选项-O2编译的代码的运行性能非常糟糕,远远落在同门的dcc32后面很远,是所有测试的C/C++编译器优化最差的,比最佳化的VC2005代码整整慢了一倍。可是,奇妙的是,未加优化选项的代码运行性能却是所有C/C++编译器中最快的,而且优化与否之间的差异不大,bcc32显然在耍着“小聪明”。进一步的探索发现,bcc32在默认情况下始终进行着类似-O1的优化。
5、FreePascal 2.0.2 (fpc) 自从FreePascal发布2.0版后,我就有意试验fpc是否已经成熟,是否能够满足我的开发要求。从测试数据上看,因为fpc与dcc32共用相同的测试源文件,但是性能上差异巨大,最佳化的fpc代码性能比dcc32还要慢接近一倍。所以,结论是fpc还未处于理想的状态下,编译器优化还有待改进。值得注意的地方是,fpc内置的RELEASE编译方案(“-OG2p3 –Xs”)并没有获得最佳的性能(这有点不可思议),而且其-O2与-O3的性能优化差异很小。经过进一步的分析与验证,发现使用-Or优化选项得到了显著的性能提升;而-Ou优化选项的使用在一般情况下没有明显的效果,但是对于特定的程序反而会产生严重的性能下降。除去特定CPU架构的-Opx以外,这已经是可以探索的最大优化参数空间了。因此,采用-O2或-O3与-Or结合的选项会达到最佳化的性能,是的,fpc已经很尽力了,但是还是落在了最后的位置。但是,对于实际的应用而言,fpc是否已经足够稳定,需要更多的测试验证,留到下次做吧。
6 参考
END.
发表于 @ 2006年04月11日 20:45:00|评论(loading...)
|