说明:文章中的很多数据可能在新的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
分享到:
相关推荐
16位高速硬件除法器的设计目标是提高除法运算的速度,这通常通过并行处理和优化算法来实现。在传统的软件除法中,除法操作通常比加法、减法或乘法慢得多,而在硬件实现中,可以通过专门的电路设计来显著减少这种延迟...
《代码优化之优化除法》将详细讲解除法运算的各种优化技巧和实际案例。而《代码优化之优化浮点数取整》则会阐述浮点数取整的不同方法及其在性能优化中的应用。 通过深入理解和实践这些优化技术,开发者能够显著提升...
现代编译器通常具备针对不同架构优化的能力,通过适当的编译器优化选项,可以利用编译器内部的算法优化除法运算。例如,在GCC编译器中可以使用"-O2"或"-O3"等优化选项来启用更高级的优化技术。 需要注意的是,上述...
"DSP代码优化技术" DIGITAL SIGNAL PROCESSOR(DSP)是一种特殊的微处理器,主要应用于实时信号处理领域。由于DSP的指令集架构和普通微处理器不同,因此在编程时需要使用特殊的优化技术来提高代码的执行速度和效率...
Verilog 代码实现的资源优化版本的除法模块,实测符合各种除法配置,已在项目中大量运用,支持32位数据除法
标题中提到的"用32-bit整数模拟32-bit和64-bit浮点数除法"是一项挑战,因为浮点数的除法操作比整数除法复杂得多。浮点数的除法涉及到指数调整、尾数相除以及处理规格化和非规格化的浮点数。而使用32-bit整数来模拟这...
MATLAB作为一款强大的数值计算软件,提供了许多工具箱来解决不同类型的优化问题,其中之一就是CVX。本实例将详细介绍如何利用CVX在MATLAB中解决凸优化问题。 CVX是一个MATLAB接口,它允许用户以高级形式表达凸优化...
对于这样的除法器,设计挑战包括处理各种边界条件,如零除、溢出和除数为零的情况,这些都需要在VHDL代码中进行适当的错误处理。 在实际应用中,高速硬件除法器的性能评估通常基于其吞吐量(每秒能完成的除法操作...
在压缩包的子文件列表中,“DIV16 - 副本_除法器_16位高速硬件除法器VHDL_高速除法器vhdl_高速除法器_DIV16-副本_源码.rar”可能是源码的实际文件,包含了具体的VHDL代码。通常,这样的文件会包含一个或多个.vhd文件...
4. **乘除法运算**: - 使用移位操作来代替乘除以2的幂次的操作,例如`a = a ;`等价于`a = a * 4;`,移位操作的效率更高。 #### 五、循环优化 优化循环中的代码可以显著减少不必要的计算,提高程序的整体性能。 ...
- **移位运算**:利用移位操作实现乘除法,如`a=a代替`a=a*4`,和`b=b>>2`代替`b=b/4`,适用于乘以或除以2的幂次。 4. **其他优化技巧**: - **避免冗余计算**:重复的计算应该被存储起来,避免重复执行。 - **...
在编程领域,代码优化是提高程序效率的关键步骤,尤其对于嵌入式系统这样的资源有限的环境。面向过程的代码优化技术主要集中在如何改进算法、数据结构以及操作细节,以提升程序运行速度和降低内存占用。下面将详细...
总结,16位高速硬件除法器VHDL设计是一项涉及数字逻辑、算法优化和硬件描述语言的综合性工作。通过理解和掌握这些知识点,我们可以更好地设计和实现高效的除法器,满足现代电子系统对计算速度的严格要求。
这个“除法器IP”文件可能包含了实现上述功能的两个Verilog源代码文件,这些文件通常会详细地定义模块接口、状态机逻辑、计算路径和其他相关细节。通过阅读和理解这些代码,我们可以学习到更多关于Verilog和除法器...
- **移位运算代替乘除法**:对于乘以或除以2的幂次,使用移位操作(`和`>>`)可以显著提高速度,如`a=a代替`a=a*4`,`b=b>>2`代替`b=b/4`。 4. **其他优化技巧**: - **避免冗余计算**:重复计算相同的表达式会...
6. **手动优化除法**: - 编译器可能无法总是将除法转换为乘法。对于简单的除法,手动转换为乘法形式,如`a * (1 / b)`代替`a / b`,可以提高效率。 7. **浮点零的检测**: - 直接比较浮点数与0可能会涉及浮点到...
在数字系统设计中,除法器是实现算术运算的关键组件之一。32位除法器主要用于处理32位整数的除法操作,广泛应用于微处理器、嵌入式系统和数字信号处理等领域。本资源提供了一个32位除法器的Verilog代码实现,这对于...
乘除法运算中,如果可能,使用位移运算符替换乘除法,这在处理2的n次幂时尤其有效。对于循环结构,应当注意减少循环次数,比如通过减少变量读取次数、循环中避免嵌套循环以及循环外声明变量等方法来优化。 在字符串...
VHDL(VHSIC Hardware Description Language)是一种用于电子设计自动化(EDA)的硬件描述语言,...在实际项目中,如果需要在FPGA或CPLD上实现除法操作,可以参考这些代码进行定制和优化,以满足特定的需求和性能要求。