汇编学习float与double速率问题

X86处理器包含两种类型的浮点数寄存器。第一种使用8个浮点寄存器组成浮点寄存器栈,另一种为向量寄存器(XMM,YMM),它们对于单双精度的处理是不同的。本文将讨论两种模式下的浮点数计算速度问题。

一、当我们编译32位程序时,使用的是x87指令集,即使用浮点寄存器堆栈进行浮点计算。此种情况下,单精度与双精度的处理是统一的,故计算速度上没有差异。我们可以做如下验证:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. float a,b,c;  
  2. c=a*b;  
  3. 汇编:  
  4. fld         dword ptr [a]  //将a加载到浮点栈顶,即ST(0)=a;  
  5. fmul        dword ptr [b]  //将栈顶元素与b相乘,结果仍存于栈顶,即ST(0)=ST(0)*b  
  6. fstp        dword ptr [c]  //将栈顶元素弹出并保存于c,即c=ST(0),POP();  
[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. double  a,b,c;  
  2. c=a*b;  
  3. 汇编:  
  4. 010213D8  fld         qword ptr [a]    
  5. 010213DB  fmul        qword ptr [b]    
  6. 010213DE  fstp        qword ptr [c]    

可以发现,上述两段代码的汇编代码完全相同.

因此,此种情况下,float与double的计算速度没有差异。在精度满足要求的情况下,可以使用float以便加载更多数据到cache,以提高cache命中率。

二、当编译64 位程序或打开MMX ,SSE,AVX指令集优化时,则使用向量寄存器。在此情况下,float与double的处理使用的是不同汇编指令,关于二者的计算速度,可以参考《Optimizingsoftware in C++》中的一段话:

Single precision division, squareroot and mathematical functions are calculated faster than double precision whenthe XMM registers are used, while the speed of addition, subtraction,multiplication, etc. is still the same regardless of precision on mostprocessors (when vector operations are not used).

意思是当使用XMM寄存器时,单精度浮点的除法、开根及一些数学函数的执行要比双精度快,但加法,减法、乘法的计算速度二者没有差异(在没有使用向量操作时)。

此处的向量操作指SIMD,即单指令多数据流。基本思想是将若干个数据加载到一个寄存器内部,一条指令可以同时处理多个数据,一个XMM(128位)可同时装载4个double或8个float,因此在使用SIMD时,一次处理的float数据量为double的两倍。

为了验证64位程序中的float和double的速度差异,下面给出测试程序:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. float SqrtfloatV1(float *A,const int len)  
  2. {  
  3.     float fSum=0;  
  4.     for (int i=0;i<len;i++)  
  5.     {  
  6.         fSum+=sqrt(A[i]);  
  7.     }  
  8.     return fSum;  
  9. }  
  10.   
  11. double SqrtdoubleV1(double *A,const int len)  
  12. {  
  13.     double dSum=0;  
  14.     for(int i=0;i<len;i++)  
  15.     {  
  16.         dSum+=sqrt(A[i]);  
  17.     }  
  18.     return dSum;  
  19. }  
测试结果:

注意事项:

一、浮点数到整数(直接截断)的转换是很低效的,使用浮点堆栈(32位程序)时需要调用函数_ftol2_sse.

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. double  a,b;  
  2. int c;  
  3. c=a*b;  
  4. 汇编:  
  5. fld         qword ptr [a]    
  6. fmul        qword ptr [b]    
  7. call        @ILT+200(__ftol2_sse) (0EC10CDh) //调用函数_ftol2_sse实现浮点数到整数的转换  
  8. mov         dword ptr [c],eax    

使用XMM寄存器(64位程序)需要指令cvttsd2si:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. double  a,b;  
  2. int c;  
  3. c=a*b;  
  4. 汇编:  
  5. movsd       xmm0,mmword ptr [a]    
  6. mulsd       xmm0,mmword ptr [b]    
  7. cvttsd2si   eax,xmm0  //cvttsd2si指令实现  
  8. mov         dword ptr [c],eax    

下面是测试程序,对一个浮点数组求和:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. float AddfloatV1(float *A,const int len)  
  2. {  
  3.     int iSum=0;  
  4.     for (int i=0;i<len;i++)  
  5.     {  
  6.         iSum+=A[i];//转成整数再求和  
  7.     }  
  8.     return (float)iSum;  
  9. }  
  10.   
  11. float AddfloatV2(float *A,const int len)  
  12. {  
  13.     float fSum=0;  
  14.     for (int i=0;i<len;i++)  
  15.     {  
  16.         fSum+=A[i];  
  17.     }  
  18.     return fSum;  
  19. }  

对于32位程序,测试结果如下:


对于64位程序,测试结果如下:


可以看出不管是32位还是64位程序,将浮点数转为整数再求和都会造成效率的大大降低。关于浮点转整数的优化的讨论,可以参考一篇博文[1].

二、使用XMM寄存器(即当编译64位程序或打开SSE2指令集时),应避免float与double混用,因为编译器需要在计算过程中进行转换。如下:

(1)float与double混用(默认的浮点常量为double)

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. float a,b;  
  2. a=b*1.2;  
  3. 汇编  
  4. movd        xmm0,dword ptr [b]    
  5. cvtps2pd    xmm0,xmm0    
  6. mulsd       xmm0,mmword ptr [__real@3ff3333333333333 (13F646790h)]    
  7. cvtsd2ss    xmm0,xmm0    
  8. movss       dword ptr [a],xmm0    

(2)纯float

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. float a,b;  
  2. a=b*1.2f;  
  3. 汇编  
  4. movss       xmm0,dword ptr [b]    
  5. mulss       xmm0,dword ptr [__real@3f99999a (13F84678Ch)]    
  6. movss       dword ptr [a],xmm0    

可以看到,(1)中多了两个指令,即cvtps2pd,cvtsd2ss,它们分别实现float转double,double转float。

下面是一段测试程序:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. float MulfloatV1(float *A,const int len)  
  2. {  
  3.     float fSum=0;  
  4.     for (int i=0;i<len;i++)  
  5.     {  
  6.         fSum+=A[i]*1.2f;  
  7.     }  
  8.     return fSum;  
  9. }  
  10.   
  11. float MulfloatV2(float *A,const int len)  
  12. {  
  13.     float fSum=0;  
  14.     for (int i=0;i<len;i++)  
  15.     {  
  16.         fSum+=A[i]*1.2;//默认的浮点常数是double  
  17.     }  
  18.     return fSum;  
  19. }  

测试结果:

完整测试程序:

Timing.h

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <windows.h>  
  2. static _LARGE_INTEGER time_start, time_over;  
  3. static double dqFreq;  
  4. static inline void startTiming()  
  5. {  
  6.     _LARGE_INTEGER f;  
  7.     QueryPerformanceFrequency(&f);  
  8.     dqFreq=(double)f.QuadPart;  
  9.   
  10.     QueryPerformanceCounter(&time_start);  
  11. }  
  12. static inline double stopTiming()  
  13. {  
  14.     QueryPerformanceCounter(&time_over);  
  15.     return ((double)(time_over.QuadPart-time_start.QuadPart)/dqFreq*1000);  
  16. }  

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <time.h>  
  3. #include <stdlib.h>  
  4. #include <math.h>  
  5. #include "Timing.h"  
  6. const int BUFSIZE =1024;  
  7. float buf[BUFSIZE];  
  8. double buf2[BUFSIZE];  
  9.   
  10. //测试64位下float与double的速度差  
  11. float SqrtfloatV1(float *A,const int len);  
  12. double SqrtdoubleV1(double *A,const int len);  
  13.   
  14. //测试浮点数转整数的速度  
  15. float AddfloatV1(float *A,const int len);  
  16. float AddfloatV2(float *A,const int len);  
  17.   
  18. //测试64位下float与double混用速度  
  19. float MulfloatV2(float *A,const int len);  
  20. float MulfloatV1(float *A,const int len);  
  21.   
  22. int main()  
  23. {  
  24.     const int testloop=50000;  
  25.     double interval;  
  26.     srand( (unsigned)time( NULL ) );    
  27.     for (int i = 0; i < BUFSIZE; i++)     
  28.     {     
  29.   
  30.         buf[i] = (float)(rand() & 0x3f);    
  31.         buf2[i]= (double)(buf[i]);  
  32.     }  
  33.     //*****************************************************************//  
  34.     //测试64位下float与double的速度差(32位无明显差异)  
  35.     volatile float result1=0;  
  36.     startTiming();        
  37.     for(unsigned int i=0;i<testloop;i++)    
  38.     {    
  39.         result1=SqrtfloatV1(buf,BUFSIZE);    
  40.     }    
  41.     interval=stopTiming();    
  42.     printf("SqrtfloatV1:\t%f,\t%lfms\n",result1,interval);  
  43.   
  44.     volatile double result2=0;  
  45.     startTiming();        
  46.     for(unsigned int i=0;i<testloop;i++)    
  47.     {    
  48.         result2=SqrtdoubleV1(buf2,BUFSIZE);    
  49.     }    
  50.     interval=stopTiming();    
  51.     printf("SqrtdoubleV1:\t%lf,\t%lfms\n",result2,interval);  
  52.     //*****************************************************************//  
  53.   
  54.     //*****************************************************************//  
  55.     //测试浮点数转整数的速度(32为与64位均有明显差异)  
  56.     volatile float result3=0;  
  57.     startTiming();        
  58.     for(unsigned int i=0;i<testloop;i++)    
  59.     {    
  60.         result3=AddfloatV1(buf,BUFSIZE);    
  61.     }    
  62.     interval=stopTiming();    
  63.     printf("AddfloatV1:\t%f,\t%lfms\n",result3,interval);  
  64.   
  65.     volatile float result4=0;  
  66.     startTiming();        
  67.     for(unsigned int i=0;i<testloop;i++)    
  68.     {    
  69.         result4=AddfloatV2(buf,BUFSIZE);    
  70.     }    
  71.     interval=stopTiming();    
  72.     printf("AddfloatV2:\t%f,\t%lfms\n",result4,interval);  
  73.     //*****************************************************************//  
  74.   
  75.     //*****************************************************************//  
  76.     //测试64位下float与double混用速度(32位无差异,因统一处理)  
  77.     volatile float result5=0;  
  78.     startTiming();        
  79.     for(unsigned int i=0;i<testloop;i++)    
  80.     {    
  81.         result5=MulfloatV1(buf,BUFSIZE);    
  82.     }    
  83.     interval=stopTiming();    
  84.     printf("MulfloatV1:\t%f,\t%lfms\n",result5,interval);  
  85.   
  86.     volatile float result6=0;  
  87.     startTiming();        
  88.     for(unsigned int i=0;i<testloop;i++)    
  89.     {    
  90.         result6=MulfloatV2(buf,BUFSIZE);    
  91.     }    
  92.     interval=stopTiming();    
  93.     printf("MulfloatV2:\t%f,\t%lfms\n",result6,interval);  
  94.     //*****************************************************************//  
  95.     return 0;  
  96. }  
  97.   
  98.   
  99.   
  100. float SqrtfloatV1(float *A,const int len)  
  101. {  
  102.     float fSum=0;  
  103.     for (int i=0;i<len;i++)  
  104.     {  
  105.         fSum+=sqrt(A[i]);  
  106.     }  
  107.     return fSum;  
  108. }  
  109.   
  110. double SqrtdoubleV1(double *A,const int len)  
  111. {  
  112.     double dSum=0;  
  113.     for(int i=0;i<len;i++)  
  114.     {  
  115.         dSum+=sqrt(A[i]);  
  116.     }  
  117.     return dSum;  
  118. }  
  119.   
  120. float AddfloatV1(float *A,const int len)  
  121. {  
  122.     int iSum=0;  
  123.     for (int i=0;i<len;i++)  
  124.     {  
  125.         iSum+=A[i];//转成整数再求和  
  126.     }  
  127.     return (float)iSum;  
  128. }  
  129.   
  130. float AddfloatV2(float *A,const int len)  
  131. {  
  132.     float fSum=0;  
  133.     for (int i=0;i<len;i++)  
  134.     {  
  135.         fSum+=A[i];  
  136.     }  
  137.     return fSum;  
  138. }  
  139.   
  140. float MulfloatV1(float *A,const int len)  
  141. {  
  142.     float fSum=0;  
  143.     for (int i=0;i<len;i++)  
  144.     {  
  145.         fSum+=A[i]*1.2f;  
  146.     }  
  147.     return fSum;  
  148. }  
  149.   
  150. float MulfloatV2(float *A,const int len)  
  151. {  
  152.     float fSum=0;  
  153.     for (int i=0;i<len;i++)  
  154.     {  
  155.         fSum+=A[i]*1.2;//默认的浮点常数是double  
  156.     }  
  157.     return fSum;  
  158. }  


参考:[1]http://blog.csdn.net/housisong/article/details/1616026


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值