Golang 效率初(粗)测
本文内容纲要:
从接触Golang开始,断断续续已有差不多一年左右的时间了,都是业余自己学学看看,尚主要限于语法及语言特性,还没有用它写过实际的项目。
关于Golang的语法及语言特性,网上有很多资源可以学习。后面某个时间,我也许会写一篇粗浅的文章,来比较一下Golang和C++、Delphi甚至C#等语言语法方面的特性。
我算是个急性子的人(当然现在好一些了),于是作为码农,显而易见会对“效率”比较敏感。这里的效率不单单指编译器生成的机器码优化程度,也包括编译器的编译速度,所以我对C++兴趣不算大,虽然它是我平时的工作语言。
言归正传。
分别用Golang、C++、Delphi写了四个小例子,包括普通的应用场景、字符串(串接)操作及数据密集计算(当然也会涉及到譬如库函数的优化等)。我的电脑软硬件环境为:Win764bit,XeonE3-1230(8核),16GRAM。Golang版本是1.3.1Windows/386,VC则用的VS2012,而Delphi则用的XE6Update1。VC和Delphi编译设置为Win32&Release,Golang则使用默认配置。
所有测试计量单位均为毫秒(ms)。
首先是计算π的例子,代码分别如下。
Golang:
packagemain
import(
"fmt"
"time"
)
constcNumMax=999999999
funcmain(){
sign:=1.0
pi:=0.0
t1:=time.Now()
fori:=1;i<cNumMax+2;i+=2{
pi+=(1.0/float64(i))*sign
sign=-sign
}
pi*=4
t2:=time.Now()
fmt.Printf("PI=%f;Time=%d\n",pi,t2.Sub(t1)/time.Millisecond)
}
C++:
#include"stdafx.h"
#include<stdio.h>
#include<time.h>
int_tmain(intargc,_TCHAR*argv[])
{
constintcNumMax=999999999;
doublesign=1.0;
doublepi=0;
clock_tt1=clock();
for(inti=1;i<cNumMax+2;i+=2)
{
pi+=(1.0f/(double)i)*sign;
sign=-sign;
}
pi*=4;
clock_tt2=clock();
printf("PI=%lf;Time=%d\n",pi,t2-t1);
return0;
}
Delphi:
programPiCalcer;
{$APPTYPECONSOLE}
{$R*.res}
uses
System.SysUtils,System.DateUtils;
const
cNumMax=999999999;
var
Sign:Double=1.0;
Pi:Double=0.0;
I:Integer;
T1:Double;
T2:Double;
S:string;
begin
T1:=Now;
I:=1;
whileI<cNumMax+2do
begin
Pi:=Pi+(1.0/I)*Sign;
Sign:=-Sign;
I:=I+2;
end;
Pi:=Pi*4;
T2:=Now;
S:=Format('PI=%.6f;Time=%d',[Pi,MilliSecondsBetween(T2,T1)]);
Writeln(S);
Readln;
end.
分别执行10次,结果如下。
Golang:2038202820362024203420152034201820242018,平均:2026.9;
C++:2041205220622036203320492039202620372038,平均:2041.3;
Delphi:2594257225742584257425642575257525712563,平均:2574.6。
结果居然很不错,比VC还快,而Delphi,大家都懂,优化向来不是它的“强项”。
然后是个质数生成例子。
Golang:
packagemain
import(
"fmt"
"time"
)
constcNumMax=10000000
funcmain(){
t1:=time.Now()
varnums[cNumMax+1]int
vari,jint
fori=2;i<cNumMax+1;i++{
nums[i]=i
}
fori=2;i<cNumMax+1;i++{
j=2
forj*i<cNumMax+1{
nums[j*i]=0
j++
}
}
cnt:=0
fori=2;i<cNumMax+1;i++{
ifnums[i]!=0{
cnt++
}
}
t2:=time.Now()
fmt.Println("Time:",t2.Sub(t1),"Count:",cnt)
}
C++:
#include"stdafx.h"
#include<stdlib.h>
#include<time.h>
constintcNumMax=10000000;
int_tmain(intargc,_TCHAR*argv[])
{
clock_tt1=clock();
int*nums=(int*)malloc(sizeof(int)*(cNumMax+1));
inti;
for(i=2;i<cNumMax+1;i++)
{
nums[i]=i;
}
intj;
for(i=2;i<cNumMax+1;i++)
{
j=2;
while(j*i<cNumMax+1)
{
nums[j*i]=0;
j++;
}
}
intcnt=0;
for(i=2;i<cNumMax+1;i++)
{
if(nums[i]!=0)
{
cnt++;
}
}
free(nums);
clock_tt2=clock();
printf("Time:%dms;Count:%d\n",t2-t1,cnt);
}
Delphi:
programPrimeSieve;
{$APPTYPECONSOLE}
{$R*.res}
uses
System.SysUtils,System.DateUtils;
const
cNumMax=10000000;
var
T1,T2:Double;
I,J:Integer;
Cnt:Integer;
Nums:arrayofInteger;
begin
T1:=Now;
SetLength(Nums,cNumMax+1);
forI:=2tocNumMaxdo
Nums[I]:=I;
forI:=2tocNumMaxdo
begin
J:=2;
whileJ*I<cNumMax+1do
begin
Nums[J*I]:=0;
Inc(J);
end;
end;
Cnt:=0;
forI:=2tocNumMaxdo
begin
ifNums[I]<>0then
Inc(Cnt);
end;
SetLength(Nums,0);
T2:=Now;
Writeln(Format('Cnt=%d;Time=%d',[Cnt,MilliSecondsBetween(T2,T1)]));
Readln;
end.
同样分别执行10次,结果如下。
Golang:959957959953961951948956956956,平均:955.6;
C++:965965967953961964963960956956,平均:961;
Delphi:973976973982981970977979971977,平均:975.9;
仍然,Golang看上去最快,而Delphi则很正常地居末。
所以我忍不住想要来一个能展现Delphi优点的例子,这个例子几乎毫无疑问,和字符串操作(及内存管理器)相关,所以有如下字符串串接的示例(其中涉及到了譬如IntToStr/itoa这样的函数调用,我自己实现了个C++版的IntToStr)。
Golang:
packagemain
import(
"bytes"
"fmt"
"strconv"
"time"
)
constcNumMax=1000000
//bytes.Buffer(7.2.6)
functestViaBuffer()string{
varbufbytes.Buffer
fori:=0;i<cNumMax;i++{
buf.WriteString(strconv.Itoa(i))
}
returnbuf.String()
}
//+=
functestViaNormal()string{
varretstring
fori:=0;i<cNumMax;i++{
ret+=strconv.Itoa(i)
}
returnret
}
funcmain(){
fmt.Println("Testviabytes.Buffer...")
t1:=time.Now()
s:=testViaBuffer()
t2:=time.Now()
fmt.Printf("Result:%s...(Length=%d);Time:%dms\n",s[2000:2005],len(s),t2.Sub(t1)/time.Millisecond)
/*
fmt.Println("Testvianormalway...")
t1=time.Now()
s=testViaNormal()
t2=time.Now()
fmt.Printf("Result:%s...(Length=%d);Time:%dms\n",s[2000:2005],len(s),t2.Sub(t1)/time.Millisecond)
*/
}
C++:
#include"stdafx.h"
#include<time.h>
#include<stdarg.h>
#include<string>
#include<iostream>
usingnamespacestd;
constintcNumMax=1000000;
wstringFormatV(constwchar_t*pwcFormat,va_listargList)
{
wstringws;
intnLen=_vscwprintf(pwcFormat,argList);
if(nLen>0)
{
ws.resize(nLen);
vswprintf_s(&ws[0],nLen+1,pwcFormat,argList);
}
returnws;
}
wstring__cdeclFormat(constwchar_t*pwcFormat,...)
{
va_listargList;
va_start(argList,pwcFormat);
wstringws=FormatV(pwcFormat,argList);
va_end(argList);
returnws;
}
stringFormatVA(constchar*pcFormat,va_listargList)
{
strings;
intnLen=_vscprintf(pcFormat,argList);
if(nLen>0)
{
s.resize(nLen);
vsprintf_s(&s[0],nLen+1,pcFormat,argList);
}
returns;
}
string__cdeclFormatA(constchar*pcFormat,...)
{
va_listargList;
va_start(argList,pcFormat);
strings=FormatVA(pcFormat,argList);
va_end(argList);
returns;
}
wstringIntToStr(intnValue)
{
returnFormat(L"%d",nValue);
}
stringIntToStrA(intnValue)
{
returnFormatA("%d",nValue);
}
wstringtestW()
{
wstringret=L"";
for(inti=0;i<cNumMax;i++)
{
ret+=IntToStr(i);
}
returnret;
}
stringtest()
{
stringret="";
for(inti=0;i<cNumMax;i++)
{
ret+=IntToStrA(i);
}
returnret;
}
int_tmain(intargc,_TCHAR*argv[])
{
cout<<"Startingtestwithaloopnumof"<<cNumMax<<endl;
clock_tt1=clock();
strings=test();
clock_tt2=clock();
cout<<"Result:"<<s.substr(2000,5)<<"..."<<";Size:"<<s.size()<<";Time:"<<t2-t1<<"ms"<<endl;
cout<<endl;
cout<<"StartingtestforWSTRINGwithaloopnumof"<<cNumMax<<endl;
t1=clock();
wstringws=testW();
t2=clock();
wcout<<"Result:"<<ws.substr(2000,5)<<"..."<<";Size:"<<ws.size()<<";Time:"<<t2-t1<<"ms"<<endl;
return0;
}
Delphi:
programStrPerformanceTest;
{$APPTYPECONSOLE}
{$R*.res}
uses
System.SysUtils,System.DateUtils;
const
cNumMax=1000000;
functionTestViaStringBuilder:string;
var
SB:TStringBuilder;
I:Integer;
begin
SB:=TStringBuilder.Create;
forI:=0tocNumMax-1do
SB.Append(IntToStr(I));
Result:=SB.ToString;
FreeAndNil(SB);
end;
functionTestViaNormal:string;
var
I:Integer;
begin
Result:='';
forI:=0tocNumMax-1do
Result:=Result+IntToStr(I);
end;
var
T1:Double;
T2:Double;
S:string;
begin
Writeln('Startingtestwithaloopnumof',cNumMax,'...');
T1:=Now;
S:=TestViaStringBuilder;
T2:=Now;
Writeln(Format('TestviaTStringBuilderresult:%s...(Length=%d);Time:%dms',[Copy(S,2001,5),Length(S),MilliSecondsBetween(T2,T1)]));
T1:=Now;
S:=TestViaNormal;
T2:=Now;
Writeln(Format('Testvianormal-way(+=)result:%s...(Length=%d);Time:%dms',[Copy(S,2001,5),Length(S),MilliSecondsBetween(T2,T1)]));
Readln;
end.
分别执行10次。悲剧的是,Golang里的字符串+=操作实在太慢了,我实在不想等下去,所以只给出了其官方推荐的使用bytes.Buffer的结果。而在这个例子中,Delphi使用TStringBuilder并未显示出什么优化(FastMM实在太强悍了!),所以我也只给出了普通的串接结果(AnsiString和string都是Delphi的原生类型,有着类同的内存布局,效率上应没有什么差别,所以这里只测试了string)。
Golang:141148134119133123145127122132,平均:132.4;
C++(std::string):384400384385389391389384390383,平均:387.9;
C++(std::wstring):519521522521519522518519518518,平均:519.7;
Delphi(string):41414141414141414441,平均:41.3;
果然,Delphi大幅领先,当然这主要归功于FastMM,这个开源的Pascal家族的内存管理器实在太强大了!
当然这个测试对C++并不公平,因为Golang的写法并非普通的串接,只是我不知道STL或Boost里有无类似StringBuilder这样的利器呢?
最后是个数据密集计算型的例子。
Golang:
packagemain
import(
"fmt"
"time"
)
constcSizeint=30
typemymatrix[cSize][cSize]int
funcmkmatrix(rows,colsint,mx*mymatrix){
rows--
cols--
count:=1
forr:=0;r<=rows;r++{
forc:=0;c<=cols;c++{
mx[r][c]=count
count++
}
}
}
funcmultmatrix(rows,colsint,m1,m2*mymatrix,mm*mymatrix){
rows--
cols--
fori:=0;i<=rows;i++{
forj:=0;j<=cols;j++{
val:=0
fork:=0;k<=cols;k++{
val+=m1[i][k]*m2[k][j]
mm[i][j]=val
}
}
}
}
funcmain(){
varm1,m2,mmmymatrix
mkmatrix(cSize,cSize,&m1)
mkmatrix(cSize,cSize,&m2)
t0:=time.Now()
fori:=0;i<=100000;i++{
multmatrix(cSize,cSize,&m1,&m2,&mm)
}
t:=time.Since(t0)
fmt.Println(mm[0][0],mm[2][3],mm[3][2],mm[4][4],mm[29][29])
fmt.Println("tick=",t)
}
C++:
#include"stdafx.h"
#include<time.h>
#include<iostream>
usingnamespacestd;
constintMATRIX_SIZE=30;
intMatrix[MATRIX_SIZE][MATRIX_SIZE];
voidMakeMatrix(introws,intcols,intmx[MATRIX_SIZE][MATRIX_SIZE])
{
rows--;
cols--;
intcount=1;
for(intr=0;r<=rows;r++)
{
for(intc=0;c<=cols;c++)
{
mx[r][c]=count;
count++;
}
}
}
voidMatrixMult(introws,intcols,constintm1[MATRIX_SIZE][MATRIX_SIZE],constintm2[MATRIX_SIZE][MATRIX_SIZE],intmx[MATRIX_SIZE][MATRIX_SIZE])
{
rows--;
cols--;
intval;
for(inti=0;i<=rows;i++)
{
for(intj=0;j<=cols;j++)
{
val=0;
for(intk=0;k<=cols;k++)
{
val+=m1[i][k]*m2[k][j];
mx[i][j]=val;
}
}
}
}
int_tmain(intargc,_TCHAR*argv[])
{
intnum=100000;
intm1[MATRIX_SIZE][MATRIX_SIZE],m2[MATRIX_SIZE][MATRIX_SIZE],mx[MATRIX_SIZE][MATRIX_SIZE];
MakeMatrix(MATRIX_SIZE,MATRIX_SIZE,m1);
MakeMatrix(MATRIX_SIZE,MATRIX_SIZE,m2);
clock_tt1=clock();
for(inti=0;i<=num;i++)
{
MatrixMult(MATRIX_SIZE,MATRIX_SIZE,m1,m2,mx);
}
clock_tt2=clock();
cout<<mx[0][0]<<""<<mx[2][3]<<""<<mx[3][2]<<""<<mx[4][4]<<endl;
cout<<t2-t1<<"ms"<<endl;
return0;
}
Delphi:
programProject1;
{$APPTYPECONSOLE}
{$R*.res}
uses
System.SysUtils,System.DateUtils;
const
cSize=30;
type
TMatrix=array[0..cSize-1,0..cSize-1]ofInteger;
procedureMakeMatrix(Rows,Cols:Integer;varMx:TMatrix);
var
R,C,Count:Integer;
begin
Dec(Rows);
Dec(Cols);
Count:=1;
forR:=0toRowsdo
forC:=0toColsdo
begin
Mx[R,C]:=Count;
Inc(Count);
end;
end;
procedureMatrixMult(Rows,Cols:Integer;constM1,M2:TMatrix;varMx:TMatrix);inline;
var
I,J,K,Val:Integer;
begin
Dec(Rows);
Dec(Cols);
forI:=0toRowsdo
forJ:=0toColsdo
begin
Val:=0;
forK:=0toColsdo
Inc(Val,M1[I,K]*M2[K,J]);
Mx[I,J]:=Val;
end;
end;
var
Num,I:Integer;
M1,M2,Mx:TMatrix;
T1,T2:Double;
begin
Num:=100000;
MakeMatrix(cSize,cSize,M1);
MakeMatrix(cSize,cSize,M2);
T1:=Now;
forI:=0toNumdo
MatrixMult(cSize,cSize,M1,M2,Mx);
T2:=Now;
WriteLn(Mx[0,0],'',Mx[2,3],'',Mx[3,2],'',Mx[4,4],'',mx[29,29]);
WriteLn('C=',MilliSecondsBetween(T2,T1),'ms');
end.
分别执行10次后结果如下。
Golang:8757879087138748873787448752875287468754,平均:8749.3;
C++:1723173517141707171317251708172317201725,平均:1719.3;
Delphi:2384236223592389236223512340235223562352,平均:2360.7;
在这样的密集运算例子里,Golang的表现实在很差,Golang的编译器优化还有很长的路。而Delphi则不出意外,不温不火,勉强也还算能接受吧。
至此,或许大致可以这样初步评断,Golang在大部分应用场景下在效率方面是满足要求的,而若涉及到密集运算,当前比较好的方法应该是要通过CGo了。考虑到Golang强大的goroutine和channel、丰富的标准库(譬如网络方面)、精简的语法和非常快速的编译速度(几乎媲美Delphi),后端开发尝试下Golang应是比较可行的,而也确实有不少早已用Golang作后端开发的项目实例了。
注:关于Golang的语言语法及并发方面的特性,过段时间再浅叙。
经由Colin同学建议,测试字符串串接中使用的自实现版IntToStr效率不行,对C++很不公平,于是我用回了_itoa_s和_itow_s这俩库函数,如下:
#include"stdafx.h"
#include<time.h>
#include<stdarg.h>
#include<string>
#include<iostream>
usingnamespacestd;
constintcNumMax=1000000;
wstringtestW()
{
wstringret=L"";
wchar_tws[10];
for(inti=0;i<cNumMax;i++)
{
_itow_s(i,ws,10);
ret+=ws;
}
returnret;
}
stringtest()
{
stringret="";
chars[10];
for(inti=0;i<cNumMax;i++)
{
_itoa_s(i,s,10);
ret+=s;
}
returnret;
}
int_tmain(intargc,_TCHAR*argv[])
{
cout<<"Startingtestwithaloopnumof"<<cNumMax<<endl;
clock_tt1=clock();
strings=test();
clock_tt2=clock();
cout<<"Result:"<<s.substr(2000,5)<<"..."<<";Size:"<<s.size()<<";Time:"<<t2-t1<<"ms"<<endl;
cout<<endl;
cout<<"StartingtestforWSTRINGwithaloopnumof"<<cNumMax<<endl;
t1=clock();
wstringws=testW();
t2=clock();
wcout<<"Result:"<<ws.substr(2000,5)<<"..."<<";Size:"<<ws.size()<<";Time:"<<t2-t1<<"ms"<<endl;
return0;
}
测试10次,效率果然大幅提升,平均大约分别是:std::string-70ms、std::wstring-75ms,相当快速!不过还是比Delphi慢了40%左右。
本文内容总结:
原文链接:https://www.cnblogs.com/ecofast/p/4043873.html