還曾經聽不少人鼓吹過腳本,說腳本程序比C++程序慢不了多少,有人甚至給10%,對此我不加評論了,看看這里的測試結果就一目了然。
下面有個浮點密集型的計算程序,沒有使用blitz++和MTL,很符合一般性應用,如果用上他們那就不好說怎么樣,因為主要是和Fortran比科學計算速度時才用。已經有人編碼測試了。
只講速度,如果再比內存,其他幾種語言就沒有必要比下去了。
不同語言版本的代碼到原作者提供的地址去下載:http://files.cnblogs.com/miloyip/smallpt20100623.zip
下面是測試用的系統配置:
測試配置
- 硬件: Intel Core i7 920@2.67Ghz(4 core, HyperThread), 12GB RAM
- 操作系統: Microsoft Windows 7 64-bit
測試名稱 |
編譯器/解譯器 |
編譯/運行選項 |
VC++ |
Visual C++ 2008 (32-bit) |
/Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /Gy /arch:SSE /fp:fast |
VC++_OpenMP |
Visual C++ 2008 (32-bit) |
/Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /Gy /arch:SSE /fp:fast /openmp |
IC++ |
Intel C++ Compiler (32-bit) |
/Ox /Og /Ob2 /Oi /Ot /Qipo /GA /MD /GS- /Gy /arch:SSE2 /fp:fast /Zi /QxHost |
IC++_OpenMP |
Intel C++ Compiler (32-bit) |
/Ox /Og /Ob2 /Oi /Ot /Qipo /GA /MD /GS- /Gy /arch:SSE2 /fp:fast /Zi /QxHost /Qopenmp |
GCC |
GCC 4.3.4 in Cygwin (32-bit) |
-O3 -march=native -ffast-math |
GCC_OpenMP |
GCC 4.3.4 in Cygwin (32-bit) |
-O3 -march=native -ffast-math -fopenmp |
C++/CLI |
Visual C++ 2008 (32-bit), .Net Framework 3.5 |
/Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /fp:fast /Zi /clr /TP |
C++/CLI_OpenMP |
Visual C++ 2008 (32-bit), .Net Framework 3.5 |
/Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /fp:fast /Zi /clr /TP /openmp |
C# |
Visual C# 2008 (32-bit), .Net Framework 3.5 |
|
*C#_outref |
Visual C# 2008 (32-bit), .Net Framework 3.5 |
|
F# |
F# 2.0 (32-bit), .Net Framework 3.5 |
|
Java |
Java SE 1.6.0_17 |
-server |
JsChrome |
Chrome 5.0.375.86 |
|
JsFirefox |
Firefox 3.6 |
|
LuaJIT |
LuaJIT 2.0.0-beta4 (32-bit) |
|
Lua |
LuaJIT (32-bit) |
-joff |
Python |
Python 3.1.2 (32-bit) |
|
*IronPython |
IronPython 2.6 for .Net 4 |
|
*Jython |
Jython 2.5.1 |
|
Ruby |
Ruby 1.9.1p378 |
|
渲染的解像度為256x256,每象素作100次采樣。
結果及分析
下表中預設的相對時間以最快的單線程測試(IC++)作基準,用鼠標按列可改變基準。由于Ruby運行時間太長,只每象素作4次采樣,把時間乘上25。另 外,因為各測試的渲染時間相差很遠,所以用了兩個棒形圖去顯示數據,分別顯示時間少于4000秒和少于60秒的測試(Ruby是4000秒以外,不予顯 示)。
Test |
Time(sec) |
Relative time |
IC++_OpenMP |
2.861 |
0.19x |
VC++_OpenMP |
3.140 |
0.21x |
GCC_OpenMP |
3.359 |
0.23x |
C++/CLI_OpenMP |
5.147 |
0.35x |
IC++ |
14.761 |
1.00x |
VC++ |
17.632 |
1.19x |
GCC |
19.500 |
1.32x |
C++/CLI |
27.634 |
1.87x |
Java |
30.527 |
2.07x |
C#_outref |
44.220 |
3.00x |
F# |
47.172 |
3.20x |
C# |
48.194 |
3.26x |
JsChrome |
237.880 |
16.12x |
LuaJIT |
829.777 |
56.21x |
Lua |
1,227.656 |
83.17x |
IronPython |
2,921.573 |
197.93x |
JsFirefox |
3,588.778 |
243.13x |
Python |
3,920.556 |
265.60x |
Jython |
6,211.550 |
420.81x |
Ruby |
77,859.653 |
5,274.69x |
C++/.Net/Java組別
靜態語言和動態語言在此測試下的性能不在同一數量級。先比較靜態語言。
C++和.Net的測試結果和上一篇博文相若,而C#和F#無顯著區別。但是,C++/CLI雖然同樣產生IL,于括管的.Net平臺上執行,其渲染時間 卻只是C#/F#的55%左右。為什么呢?使用ildasm去反匯編C++/CLI和C#的可執行文件后,可以發現,程序的熱點函數 Sphere.Intersect()在兩個版本中,C++/CLI版本的代碼大小(code size)為201字節, C#則為125字節! C++/CLI版本在編譯時,已把函數內所有Vec類的方法調用全部內聯,而C#版本則使用callvirt調用Vec的方法。估計JIT沒有把這函數進 行內聯,做成這個性能差異。另外,C++/CLI版本使用了值類型,并使用指針(代碼中為引用)托管代碼(C++/CLI)的渲染時間,僅為原生非括管代碼(IC++)的1.91倍,個人覺得.Net的JIT已經非常不錯。
另一方面,Java的性能表現非常突出,只比C++/CLI稍慢一點,Java版本的渲染時間為C#/F#的65%左右。以前一直認為,C#不少設計會使其性能高于Java,例如C#的方法預設為非虛,Java則預設為虛;又例如C#支持struct作值類型(value type),Java則只有class引用類型(reference type),后者必須使用GC。但是,這個測試顯示,Java VM應該在JIT中做了大量優化,估計也應用了內聯,才能使其性能逼近C++/CLI。
純C++方面,Intel C++編譯器最快,Visual C++慢一點點(1.19x),GCC再慢一點點(1.32x)。這結果符合本人預期。 Intel C++的OpenMP版本和單線程比較,達5.16加速比(speedup),對于4核Hyper Threading來說算是不錯的結果。讀者若有興趣,也可以自行測試C# 4.0的并行新特性。
動態語言組別
首先,要說一句,Google太強了,難以想像JsChome的渲染時間僅是IC++的16.12倍,C#的4.94倍。
以下比較各動態語言的相對時間,以JsChrome為基準。 Chrome的V8 JavaScript引擎(1.00x)大幅拋離Firefox的SpiderMonkey引擎(15.09x)。而LuaJIT(3.49x)和Lua(5.16x)則排第二和第三名。 Lua的JIT版本是沒有JIT的68%,并沒有想像中的快,但是也比Python(16.48x)快得多。曾聽說過Ruby有效能問題,沒想到問題竟然如此嚴重(327.31x),其渲染時間差不多是Python的20倍.
#include <math.h> // smallpt, a Path Tracer by Kevin Beason, 2008
#include <stdlib.h> // Make : g++ -O3 -fopenmp smallpt.cpp -o smallpt
#include <stdio.h> // Remove "-fopenmp" for g++ version < 4.2
#include <time.h> // MILO
#include "erand48.inc" // MILO
#define M_PI 3.141592653589793238462643 // MILO
struct Vec { // Usage: time ./smallpt 5000 && xv image.ppm
double x, y, z; // position, also color (r,g,b)
Vec(double x_=0, double y_=0, double z_=0){ x=x_; y=y_; z=z_; }
Vec operator+(const Vec &b) const { return Vec(x+b.x,y+b.y,z+b.z); }
Vec operator-(const Vec &b) const { return Vec(x-b.x,y-b.y,z-b.z); }
Vec operator*(double b) const { return Vec(x*b,y*b,z*b); }
Vec mult(const Vec &b) const { return Vec(x*b.x,y*b.y,z*b.z); }
Vec& norm(){ return *this = *this * (1/sqrt(x*x+y*y+z*z)); }
double dot(const Vec &b) const { return x*b.x+y*b.y+z*b.z; } // cross:
Vec operator%(const Vec &b){return Vec(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);}
};
struct Ray { Vec o, d; Ray(const Vec &o_, const Vec &d_) : o(o_), d(d_) {} };
enum Refl_t { DIFF, SPEC, REFR }; // material types, used in radiance()
struct Sphere {
double rad; // radius
Vec p, e, c; // position, emission, color
Refl_t refl; // reflection type (DIFFuse, SPECular, REFRactive)
Sphere(double rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_):
rad(rad_), p(p_), e(e_), c(c_), refl(refl_) {}
double intersect(const Ray &r) const { // returns distance, 0 if nohit
Vec op = p-r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0
double t, eps=1e-4, b=op.dot(r.d), det=b*b-op.dot(op)+rad*rad;
if (det<0) return 0; else det=sqrt(det);
return (t=b-det)>eps ? t : ((t=b+det)>eps ? t : 0);
}
};
Sphere spheres[] = {//Scene: radius, position, emission, color, material
Sphere(1e5, Vec( 1e5+1,40.8,81.6), Vec(),Vec(.75,.25,.25),DIFF),//Left
Sphere(1e5, Vec(-1e5+99,40.8,81.6),Vec(),Vec(.25,.25,.75),DIFF),//Rght
Sphere(1e5, Vec(50,40.8, 1e5), Vec(),Vec(.75,.75,.75),DIFF),//Back
Sphere(1e5, Vec(50,40.8,-1e5+170), Vec(),Vec(), DIFF),//Frnt
Sphere(1e5, Vec(50, 1e5, 81.6), Vec(),Vec(.75,.75,.75),DIFF),//Botm
Sphere(1e5, Vec(50,-1e5+81.6,81.6),Vec(),Vec(.75,.75,.75),DIFF),//Top
Sphere(16.5,Vec(27,16.5,47), Vec(),Vec(1,1,1)*.999, SPEC),//Mirr
Sphere(16.5,Vec(73,16.5,78), Vec(),Vec(1,1,1)*.999, REFR),//Glas
Sphere(600, Vec(50,681.6-.27,81.6),Vec(12,12,12), Vec(), DIFF) //Lite
};
inline double clamp(double x){ return x<0 ? 0 : x>1 ? 1 : x; }
inline int toInt(double x){ return int(pow(clamp(x),1/2.2)*255+.5); }
inline bool intersect(const Ray &r, double &t, int &id){
double n=sizeof(spheres)/sizeof(Sphere), d, inf=t=1e20;
for(int i=int(n);i--;) if((d=spheres[i].intersect(r))&&d<t){t=d;id=i;}
return t<inf;
}
Vec radiance(const Ray &r, int depth, unsigned short *Xi){
double t; // distance to intersection
int id=0; // id of intersected object
if (!intersect(r, t, id)) return Vec(); // if miss, return black
const Sphere &obj = spheres[id]; // the hit object
Vec x=r.o+r.d*t, n=(x-obj.p).norm(), nl=n.dot(r.d)<0?n:n*-1, f=obj.c;
double p = f.x>f.y && f.x>f.z ? f.x : f.y>f.z ? f.y : f.z; // max refl
if (++depth>5) if (erand48(Xi)<p) f=f*(1/p); else return obj.e; //R.R.
if (depth > 100) return obj.e; // MILO
if (obj.refl == DIFF){ // Ideal DIFFUSE reflection
double r1=2*M_PI*erand48(Xi), r2=erand48(Xi), r2s=sqrt(r2);
Vec w=nl, u=((fabs(w.x)>.1?Vec(0,1):Vec(1))%w).norm(), v=w%u;
Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm();
return obj.e + f.mult(radiance(Ray(x,d),depth,Xi));
} else if (obj.refl == SPEC) // Ideal SPECULAR reflection
return obj.e + f.mult(radiance(Ray(x,r.d-n*2*n.dot(r.d)),depth,Xi));
Ray reflRay(x, r.d-n*2*n.dot(r.d)); // Ideal dielectric REFRACTION
bool into = n.dot(nl)>0; // Ray from outside going in?
double nc=1, nt=1.5, nnt=into?nc/nt:nt/nc, ddn=r.d.dot(nl), cos2t;
if ((cos2t=1-nnt*nnt*(1-ddn*ddn))<0) // Total internal reflection
return obj.e + f.mult(radiance(reflRay,depth,Xi));
Vec tdir = (r.d*nnt - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t)))).norm();
double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c = 1-(into?-ddn:tdir.dot(n));
double Re=R0+(1-R0)*c*c*c*c*c,Tr=1-Re,P=.25+.5*Re,RP=Re/P,TP=Tr/(1-P);
return obj.e + f.mult(depth>2 ? (erand48(Xi)<P ? // Russian roulette
radiance(reflRay,depth,Xi)*RP:radiance(Ray(x,tdir),depth,Xi)*TP) :
radiance(reflRay,depth,Xi)*Re+radiance(Ray(x,tdir),depth,Xi)*Tr);
}
int main(int argc, char *argv[]){
clock_t start = clock(); // MILO
int w=512, h=512, samps = argc==2 ? atoi(argv[1])/4 : 250; // # samples
Ray cam(Vec(50,52,295.6), Vec(0,-0.042612,-1).norm()); // cam pos, dir
Vec cx=Vec(w*.5135/h), cy=(cx%cam.d).norm()*.5135, r, *c=new Vec[w*h];
#pragma omp parallel for schedule(dynamic, 1) private(r) // OpenMP
for (int y=0; y<h; y++){ // Loop over image rows
fprintf(stderr,"\rRendering (%d spp) %5.2f%%",samps*4,100.*y/(h-1));
unsigned short Xi[3]={0,0,y*y*y}; // MILO
for (unsigned short x=0; x<w; x++) // Loop cols
for (int sy=0, i=(h-y-1)*w+x; sy<2; sy++) // 2x2 subpixel rows
for (int sx=0; sx<2; sx++, r=Vec()){ // 2x2 subpixel cols
for (int s=0; s<samps; s++){
double r1=2*erand48(Xi), dx=r1<1 ? sqrt(r1)-1: 1-sqrt(2-r1);
double r2=2*erand48(Xi), dy=r2<1 ? sqrt(r2)-1: 1-sqrt(2-r2);
Vec d = cx*( ( (sx+.5 + dx)/2 + x)/w - .5) +
cy*( ( (sy+.5 + dy)/2 + y)/h - .5) + cam.d;
r = r + radiance(Ray(cam.o+d*140,d.norm()),0,Xi)*(1./samps);
} // Camera rays are pushed ^^^^^ forward to start in interior
c[i] = c[i] + Vec(clamp(r.x),clamp(r.y),clamp(r.z))*.25;
}
}
printf("\n%f sec\n", (float)(clock() - start)/CLOCKS_PER_SEC); // MILO
FILE *f = fopen("image.ppm", "w"); // Write image to PPM file.
fprintf(f, "P3\n%d %d\n%d\n", w, h, 255);
for (int i=0; i<w*h; i++)
fprintf(f,"%d %d %d ", toInt(c[i].x), toInt(c[i].y), toInt(c[i].z));
}
ruby整數運算也就慢30倍左右。
動態語言就指著類庫呢,那些都是c寫的,所以總體速度差距沒那么大。