性能优化不管是从方法论,还是从实践上都有很多东西。本文从C++语本身入手,介绍了一些性能优化的方法,希望能做到简洁实用。
实例1在开始本文的内容之前,让我们看段小程序:
//获取一个整数对应10近制的位数uint32_tdigits10_v1(uint64_tv){uint32_tresult=0;do{++result;v/=10;}while(v);returnresult;}
如果要对这段代码进行优化,你认为瓶颈会是什么呢?代码-g-O2后看一眼汇编:
Dumpofassemblercodeforfunctiondigits10_v1(uint64_t):0x08f0digits10_v1(uint64_t)+0:mov%rdi,%rdx0x08f3digits10_v1(uint64_t)+3:xor%esi,%esi0x08f5digits10_v1(uint64_t)+5:mov0xcccccccccccccccd,%rcx0x08ffdigits10_v1(uint64_t)+15:nop0xdigits10_v1(uint64_t)+16:mov%rdx,%rax0xdigits10_v1(uint64_t)+19:add0x1,%esi0xdigits10_v1(uint64_t)+22:mul%rcx0xdigits10_v1(uint64_t)+25:shr0x3,%rdx0xddigits10_v1(uint64_t)+29:test%rdx,%rdx0xdigits10_v1(uint64_t)+32:jne0x40digits10_v1(uint64_t)+xdigits10_v1(uint64_t)+34:mov%esi,%eax0xdigits10_v1(uint64_t)+36:retq/*注:对于常数的除法操作,编译器一般会转换成乘法+移位的方式,即a/b=a*(1/b)=a*(2^n/b)*(1/2^n)=a*(2^n/b)n.这里的n=3,b=10,2^n/b=4/5,0xcccccccccccccccd是编译器对4/5的定点算法表示*/
指令已经很少了,有多少优化空间呢?先不着急,看看下面这段代码
uint32_tdigits10_v2(uint64_tv){uint32_tresult=1;for(;;){if(v10)returnresult;if(v)returnresult+1;if(v0)returnresult+2;if(v00)returnresult+3;//Skipaheadby4ordersofmagnitudev/=00U;result+=4;}}uint32_tdigits10_v3(uint64_tv){if(v10)return1;if(v)return2;if(v0)return3;if(v0000000000){//10^12if(v000000){//10^7if(v0000){//10^6if(v00)return4;return5+(v=000);//10^5}return7+(v=00000);//10^7}if(v00000000){//10^10return9+(v=0000000);//10^9}return11+(v=000000000);//10^11}return12+digits10_v3(v/0000000000);//10^12}
写了一个小程序,digits10_v2比digits10_v1快了45%,digits10_v3比digits10_v1快了60%+。不难看出测试结论跟数据的取值范围相关,就本例来说数值越大,提升越明显。是什么原因呢?附测试程序:
intmain(){srand();uint64_tdigit10_array[ITEM_COUNT];for(inti=0;iITEM_COUNT;++i){digit10_array[i]=rand();}structtimevalstart,end;//digits10_v1uint64_tsum1=0;uint64_ttime1=0;gettimeofday(start,NULL);for(inti=0;iRUN_TIMES;++i){sum1+=digits10_v1(digit10_array[i]);}gettimeofday(end,NULL);time1=(end.tv_sec-start.tv_sec)*0*0+end.tv_usec-start.tv_usec;//digits10_v2uint64_tsum2=0;uint64_ttime2=0;gettimeofday(start,NULL);for(inti=0;iRUN_TIMES;++i){sum2+=digits10_v2(digit10_array[i]);}gettimeofday(end,NULL);time2=(end.tv_sec-start.tv_sec)*0*0+end.tv_usec-start.tv_usec;//digits10_v3uint64_tsum3=0;uint64_ttime3=0;gettimeofday(start,NULL);for(inti=0;iRUN_TIMES;++i){sum3+=digits10_v3(digit10_array[i]);}gettimeofday(end,NULL);time3=(end.tv_sec-start.tv_sec)*0*0+end.tv_usec-start.tv_usec;cout"sum1:"sum1"\tsum2:"sum2"\tsum3:"sum3endl;cout"cost1:"time1"us\tcost2:"time2"us\tcost3:"time3"us""\tcost2/cost1:"(1.0*time2)/time1"\tcost3/cost1:"(1.0*time3)/time1endl;return0;}/*
执行结果:
g++-g-O2cplusplus_optimize.cpp./a.outsum1:sum2:sum3:cost1:uscost2:uscost3:uscost2/cost1:0.cost3/cost1:0.*/
Strengthreduction
优化原因不是因为做了循环展开,而是由于不同指令本身的速度就是不一样的,比较、整型的加减、位操作速度都是最快的,而除法/取余却很慢。下面有一个更详细的列表,为了更直观一些,用了clockcycle来衡量,不过这里的clockcycle是个平均值,不同的CPU还是稍有差异:
*