`
micheal19840929
  • 浏览: 163275 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

代码优化之-优化除法

    博客分类:
  • J2SE
阅读更多

说明:文章中的很多数据可能在新的CPU或不同的CPU或不同的系统环境下有不同的结果,可能不能面面俱到)
  x86系列的CPU对于位运算、加、减等基本指令都能在1个CPU周期内完成(现在的CPU还能乱序执行,从而使指令的平均CPU周期更小);现在的CPU,做乘法也是很快的(需要几个CPU周期,每个周期可能启动一个新的乘指令),但作为基本指令的除法却超出很多人的预料,它是一条很慢的操作,整数和浮点的除法都慢;我测试的英特尔P5赛扬CPU浮点数的除法差不多是37个CPU周期,整数的除法是80个CPU周期,AMD2200+浮点数的除法差不多是21个CPU周期,整数的除法是40个CPU周期。(改变FPU运算精度对于除法无效) (SSE指令集的低路单精度数除法指令DIVPS 18个CPU周期,四路单精度数除法指令DIVSS 36个CPU周期)   (x86求余运算和除法运算是用同一条CPU指令实现的; 据说,很多CPU的整数除法都是用数学协处理器的浮点除法器完成的;有一个推论就是,浮点除法和整数除法不能并行执行.  (ps:intel的p4 imul指令可能有14周期(或15-18)的延迟才能得到结果)

  本文将给出一些除法的优化方法或替代算法  (警告:某些替代算法并不能保证完全等价!)

  1.尽量少用除法
   比如: if (x/y>z) ...
   改成: if ( ((y>0)&&(x>y*z))||((y<0)&&(x<y*z)) ) ...

   (少用求余)
   比如: ++index; if (index>=count) index=index % count;  //assert(index<count*2);
   改成: ++index; if (index>=count) index=index - count;

  2.用减法代替除法
   如果知道被除数是除数的很小的倍数,那么可以用减法来代替除法
   比如:  uint32 x=200;
         uint32 y=70;
           uint32 z=x/y;
   改成:  uint z=0;
           while (x>=y)
           {
              x-=y;  ++z;
           }

   一个用减法和移位完成的除法 (如果你没有除法指令可用:)
     uint32 div(uint64 u,uint32 z) //return u/z  
     {
         uint32 x=(uint32)(u>>32);
         uint32 y=(uint32)u;
         //y保存商 x保存余数

         for (int i=0;i<32;++i)
         {
             uint32 t=((int32)x) >> 31;
             x=(x<<1)|(y>>31);
             y=y<<1;
             if ((x|t)>=z)
             {
                 x-=z;
                 ++y;
             }
         }
         return y;
     }
     (该函数经过了测试;z==0需要自己处理;对于有符号除法,可以用取绝对值的方法(当然也不是轻松就能
写出完全等价的有符号除法的:); 如果不需s的64bit长度,仅需要32bit,那么可以化简这个函数,但改进不多)
  
  3.用移位代替除法  (很多编译器能自动做好这个优化)
   要求除数是2的次方的常量; (同理:对于某些应用,可以优先选取这样的数来做除数)
   比如:  uint32 x=213432575;
         uint32 y=x/8;
   改成:  y=x>>3;

   对于有符号的整数;
   比如:  int32 x=213432575;
         int32 y=x/8;
   改成:  if (x>=0)  y=x>>3; 
           else  y=(x+(1<<3-1))>>3;

  4.合并除法   (替代方法不等价,很多编译器都不会帮你做这种优化)
   适用于不能用其它方法避免除法的时候;
   比如:  double x=a/b/c;
   改成:  double x=a/(b*c);

   比如:  double x=a/b+c/b;
   改成:  double x=(a+c)/b;

   比如:  double x=a/b;
    double y=c/d;
    double z=e/f;
   改成:  double tmp=1.0/(b*d*f);
           double x=a*tmp*d*f;
           double y=c*tmp*b*f;
           double z=e*tmp*b*d;   
  
  5.把除法占用的时间充分利用起来
   CPU在做除法的时候,可以不用等待该结果(也就是后面插入的指令不使用该除法结果),而插入多条简单整数
指令(不包含整数除法,而且结果不能是一个全局或外部变量等),把除法占用的时间节约出来;
   (当除法不可避免的时候,这个方法很有用)

  6.用查表的方法代替除法
    (适用于除数和被除数的可能的取值范围较小的情况,否则空间消耗太大)
    比如   uint8  x;  uint8 y;
         uint8  z=x/y;
    改成   uint8  z=table[x][y];    //其中table是预先计算好的表,table[i][j]=i/j; 
           //对于除零的情况需要根据你的应用来处理
    或者:uint8  z=table[x<<8+y];  //其中table[i]=(i>>8)/(i&(1<<8-1));

    比如   uint8  x;
         uint8  z=x/17;
    改成   uint8  z=table[x];    //其中table[i]=i/17; 

  7.用乘法代替除法
     (替代方法不等价,很多编译器都不会帮你做这种优化)
     比如:  double x=y/21.3432575;
     改成:  double x=y*(1/21.3432575); //如果编译器不能优化掉(1/21.3432575),请预先计算出该结果

   对于整数,可以使用与定点数相似的方法来处理倒数
   (该替代方法不等价)
   比如:  uint32 x,y;  x=y/213;
   改成:  x=y*((1<<16)/213)  >> 16;
   某些应用中y*((1<<16)/213)可能会超出值域,这时候可以考虑使用int64来扩大值域
           uint32 x=((uint64)y)*((1<<31)/213)  >> 31;
      也可以使用浮点数来扩大值域
           uint32 x=(uint32)(y*(1.0/213)); (警告: 浮点数强制类型转换到整数在很多高级语言里都是
一条很慢的操作,在下几篇文章中将给出其优化的方法)

   对于这种方法,某些除法是有与之完全等价的优化方法的:
     无符号数除以3:  uint32 x,y;   x=y/3;
     推理:                       y               y          y               (1<<33)+1   y     
             x=y/3  =>  x=[-]  =>  x=[- + ---------]  => x=[--------- * -------] // []表示取整
                                   3               3  3*(1<<33)             3       (1<<33)
                                        y        1             
              (可证明: 0 <= --------- < - )
                              3*(1<<33)   3                                          
             即: x=(uint64(y)*M)>>33;  其中魔法数M=((1<<33)+1)/3=2863311531=0xAAAAAAAB;

     无符号数除以5:  uint32 x,y;   x=y/5;  (y<(1<<31))
     推理:                       y               y      3*y               (1<<33)+3    y     
             x=y/5  =>  x=[-]  =>  x=[- + ---------]  => x=[--------- * -------]
                                   5               5   5*(1<<33)             5       (1<<33)
                                      3*y      1             
              (可证明: 0 <= --------- < - )
                               5*(1<<33)   5                                                          
             即: x=(uint64(y)*M)>>33;  其中魔法数M=((1<<33)+3)/5=1717986919=0x66666667;

     无符号数除以7:  uint32 x,y;   x=y/7; 
     推理 :略                                                        
          即:x=((uint64(y)*M)>>33+y)>>3; 其中魔法数M=((1<<35)+3)/7-(1<<32)=613566757=0x24924925;

     对于这种完全等价的替代,还有其他替代公式不再讨论,对于有符号除法的替代算法没有讨论,很多数都有完全等价的替代算法,那些魔法数也是有规律可循的;有“进取心”的编译器应该帮助用户处理掉这个优化(vc6中就已经见到!  偷懒的办法是直接看vc6生成的汇编码:);

  8.对于已知被除数是除数的整数倍数的除法,能够得到替代算法;或改进的算法;
    这里只讨论除数是常数的情况;
    比如对于(32位除法):x=y/a; //已知y是a的倍数,并假设a是奇数
    (如果a是偶数,先转化为a=a0*(1<<k); y/a==(y/a0)>>k;a0为奇数)
    求得a',使 (a'*a)  % (1<<32) =  1;
    那么: x=y/a  => x=(y/a)*((a*a')  %(1<<32))  =>  x=(y*a') % (1<<32);  //这里并不需要实际做一个求余指令  
    (该算法可以同时支持有符号和无符号除法)
    比如   uint32  y;
         uint32  x=y/7;   //已知y是7的倍数
    改成   uint32  x=(uint32)(y*M);   //其中M=(5*(1<<32)+1)/7
 
  9.近似计算除法 (该替代方法不等价)
     优化除数255的运算(257也可以,或者1023,1025等等)(1026也可以,推导出的公式略有不同)
     比如颜色处理中 :  uint8 color=colorsum/255;
     改成:             uint8 color=colorsum/256;  也就是 color=colorsum>>8;
     这个误差在颜色处理中很多时候都是可以接受的
     如果要减小误差可以改成:     uint8 color=(colorsum+(colorsum>>8))>>8; 
       推导: x/255=(x+x/255)/(255+1)=(x+A)>>8; A=x/255;
       把A改成A=x>>8 (引入小的误差);带入公式就得到了: x/255约等于(x+(x>>8))>>8的公式
       同理可以有x/255约等于(x+(x>>8)+(x>>16))>>8等其它更精确的公式(请推导出误差项已确定是否精度足够)
    
  10. 牛顿迭代法实现除法
     (很多CPU的内部除法指令就是用该方法或类似的方法实现的)
     假设有一个函数y=f(x);  求0=f(x)时,x的值;(这里不讨论有多个解的情况或逃逸或陷入死循环或陷入混沌状态的问题)
    
                                 (参考图片)
     求函数的导函数为 y=f'(x);   //可以这样来理解这个函数的意思:x点处函数y=f(x)的斜率;
     a.取一个合适的x初始值x0; 并得到y0=f(x0);
     b.过(x0,y0)作一条斜率为f'(x0)的直线,与x轴交于x1;
     c.然后用得到的x1作为初始值,进行迭代;
     当进行足够多次的迭代以后,认为x1将会非常接近于方程0=f(x)的解,这就是牛顿迭代;
     把上面的过程化简,得到牛顿迭代公式: x(n+1)=x(n)-y(x(n))/y'(x(n))
    
     这里给出利用牛顿迭代公式求倒数的方法; (用倒数得到除法: y = x/a = x* (1/a) )
        求1/a,  
        令a=1/x;  有方程 y=a-1/x;
        求导得y'=1/x^2;
        代入牛顿迭代方程 x(n+1)=x(n)-y(x(n))/y'(x(n));
        有迭代式 x_next=(2-a*x)*x; //可证明:该公式为2阶收敛公式; 也就是说计算出的解的有效精度成倍增长;
      
     证明收敛性:令x=(1/a)+dx;  //dx为一个很小的量
     则有x_next-(1/a)=(2-a*(1/a+dx))*(1/a+dx)-1/a
                     =(-a)*dx^2  //^表示指数运算符
      证毕.
   
    程序可以用该方法来实现除法,并按照自己的精度要求来决定迭代次数;
    (对于迭代初始值,可以使用查表法来得到,或者利用IEEE浮点格式得到一个近似计算的表达式;在SSE指令集中有一条RCPPS(RCPSS)指令也可以用来求倒数的近似值;有了初始值就可以利用上面的迭代公式得到更精确的结果)

    附录: 用牛顿迭代法来实现开方运算  
       //开方运算可以表示为 y=x^0.5=1/(1/x^0.5); 先求1/x^0.5
       求1/a^0.5,  
       令a=1/x^2;  有方程y=a-1/x^2;
       求导得y'=2/x^3;
       代入牛顿方程 x(n+1)=x(n)-y(x(n))/y'(x(n));
       有迭代式 x_next=(3-a*x*x)*x*0.5; //可证明:该公式为2阶收敛公式  //方法同上 证明过程略
 
  11. 用快速乘法器实现快速除法
     这是存在的一种快速除法算法,当前最好用硬件实现,请查阅相关资料

 

<<代码优化-之-优化除法>>   作者:HouSisong@263.net

分享到:
评论

相关推荐

    DIV16 - 副本_除法器_16位高速硬件除法器VHDL_高速除法器vhdl_高速除法器_DIV16-副本

    16位高速硬件除法器的设计目标是提高除法运算的速度,这通常通过并行处理和优化算法来实现。在传统的软件除法中,除法操作通常比加法、减法或乘法慢得多,而在硬件实现中,可以通过专门的电路设计来显著减少这种延迟...

    代码优化之条件分支预测、除法、浮点数取整

    《代码优化之优化除法》将详细讲解除法运算的各种优化技巧和实际案例。而《代码优化之优化浮点数取整》则会阐述浮点数取整的不同方法及其在性能优化中的应用。 通过深入理解和实践这些优化技术,开发者能够显著提升...

    资源优化的Verilog实现除法模块

    Verilog 代码实现的资源优化版本的除法模块,实测符合各种除法配置,已在项目中大量运用,支持32位数据除法

    利用cvx 解决凸优化问题实例代码.rar_matlab 凸优化_凸优化_凸优化程序_凸优化问题_利用cvx 解决凸优化问题实例

    MATLAB作为一款强大的数值计算软件,提供了许多工具箱来解决不同类型的优化问题,其中之一就是CVX。本实例将详细介绍如何利用CVX在MATLAB中解决凸优化问题。 CVX是一个MATLAB接口,它允许用户以高级形式表达凸优化...

    用32-bit整数模拟32-bit和64-bit浮点数除法

    标题中提到的"用32-bit整数模拟32-bit和64-bit浮点数除法"是一项挑战,因为浮点数的除法操作比整数除法复杂得多。浮点数的除法涉及到指数调整、尾数相除以及处理规格化和非规格化的浮点数。而使用32-bit整数来模拟这...

    DSP的代码优化求余运算,用移位实现乘除法运算,延时函数

    "DSP代码优化技术" DIGITAL SIGNAL PROCESSOR(DSP)是一种特殊的微处理器,主要应用于实时信号处理领域。由于DSP的指令集架构和普通微处理器不同,因此在编程时需要使用特殊的优化技术来提高代码的执行速度和效率...

    高速硬件除法器_除法器_高速除法器_ownerny7_edavhdl_高速除法器vhdl_

    对于这样的除法器,设计挑战包括处理各种边界条件,如零除、溢出和除数为零的情况,这些都需要在VHDL代码中进行适当的错误处理。 在实际应用中,高速硬件除法器的性能评估通常基于其吞吐量(每秒能完成的除法操作...

    DIV16 - 副本_除法器_16位高速硬件除法器VHDL_高速除法器vhdl_高速除法器_DIV16-副本.zip

    在压缩包的子文件列表中,“DIV16 - 副本_除法器_16位高速硬件除法器VHDL_高速除法器vhdl_高速除法器_DIV16-副本_源码.rar”可能是源码的实际文件,包含了具体的VHDL代码。通常,这样的文件会包含一个或多个.vhd文件...

    c代码优化方案

    - **移位运算**:利用移位操作实现乘除法,如`a=a代替`a=a*4`,和`b=b&gt;&gt;2`代替`b=b/4`,适用于乘以或除以2的幂次。 4. **其他优化技巧**: - **避免冗余计算**:重复的计算应该被存储起来,避免重复执行。 - **...

    面向过程的代码优化技术

    在编程领域,代码优化是提高程序效率的关键步骤,尤其对于嵌入式系统这样的资源有限的环境。面向过程的代码优化技术主要集中在如何改进算法、数据结构以及操作细节,以提升程序运行速度和降低内存占用。下面将详细...

    DIV16 - 副本_除法器_16位高速硬件除法器VHDL_高速除法器vhdl_高速除法器_DIV16-副本_源码.zip

    总结,16位高速硬件除法器VHDL设计是一项涉及数字逻辑、算法优化和硬件描述语言的综合性工作。通过理解和掌握这些知识点,我们可以更好地设计和实现高效的除法器,满足现代电子系统对计算速度的严格要求。

    除法器IP verilog

    这个“除法器IP”文件可能包含了实现上述功能的两个Verilog源代码文件,这些文件通常会详细地定义模块接口、状态机逻辑、计算路径和其他相关细节。通过阅读和理解这些代码,我们可以学习到更多关于Verilog和除法器...

    Delphi代码优化

    6. **手动优化除法**: - 编译器可能无法总是将除法转换为乘法。对于简单的除法,手动转换为乘法形式,如`a * (1 / b)`代替`a / b`,可以提高效率。 7. **浮点零的检测**: - 直接比较浮点数与0可能会涉及浮点到...

    32位除法器设计Verilog代码.zip

    在数字系统设计中,除法器是实现算术运算的关键组件之一。32位除法器主要用于处理32位整数的除法操作,广泛应用于微处理器、嵌入式系统和数字信号处理等领域。本资源提供了一个32位除法器的Verilog代码实现,这对于...

    除法器的Verilog实现

    Verilog代码会定义这些接口,并实现除法运算的逻辑流程,包括预处理(如符号判断)、迭代计算、结果修正等步骤。 在设计过程中,除法器通常采用分治策略,将大问题分解为小问题解决,例如将除法转换为多次减法和...

    devided_除法器_

    在压缩包文件"devided"中,虽然没有具体的代码或设计文件,但我们可以想象其中可能包含了Verilog或VHDL的源代码,这些代码详细描述了除法器的逻辑结构和工作流程。设计者可能还使用了IP核(Intellectual Property ...

    C代码优化方案11条

    - **移位运算替代乘除法**:`a = a * 4` 和 `b = b / 4` 可以用 `a = a 和 `b = b &gt;&gt; 2` 替换。 4. **避免冗余计算** 重复计算可能导致不必要的性能损失。例如,如果一个值在循环中多次使用,应该将其存储在临时...

    C语言代码优化11种实用方法.pdf

    - **利用移位运算**:移位运算`和`&gt;&gt;`可以代替乘除法,如`a=a替换`a=a*4`,`b=b&gt;&gt;2`替换`b=b/4`,这在处理2的幂次方的乘除时尤其有用。 4. **其他优化技巧**: - **避免不必要的函数调用**:如上所示,避免使用`...

    Vivado下verilog除法器(较少资源占用)

    此压缩包中的"Module_Div.v"文件很可能是实现除法器的Verilog源代码。它可能包含了输入接口(被除数和除数),控制信号(如启动和停止),以及输出(商和余数)。代码中可能采用了某种优化技术,如分治法或者位倒序...

    算法-带余除法(信息学奥赛一本通-T1009)(包含源程序).rar

    《算法-带余除法(信息学奥赛一本通-T1009)》是一部针对信息学竞赛的教程,特别关注计算机科学中的一个重要基础概念——带余除法。带余除法是数学和计算机科学中处理整数除法时的一个基本工具,它不仅在算法设计中...

Global site tag (gtag.js) - Google Analytics