转自 http://dev.gameres.com/Program/Other/fudianOp.htm
现在的编译器都能够针对浮点指令做优化,但是,我还是想你推荐VC,我认为,VC的优化更好,它能够更好地利用Pentium系列处理器的流水线。
· 优化概略
· 尽量理解你的编译器处理浮点指令的原理,要知道,你不可能把一个程序完全用浮点指令来写,更多的代码还是基于高级语言的。
· 找出程序的关键所在,例如循环等代码,这些是真正影响效率的地方。
· 分离相关代码。
· 注意解决对内存带宽的需求。
· 检查是否有较长延迟的指令频繁使用,尽量减少。
· 如果不必要,尽量使用较低的精度。在很多指令操作中,使用较低的精度会获得较高的速度。这样也可以节省内存。
· 让你的结果在精度允许的范围内波动,超过精度范围的运算将会带来极大的开销。
· 善用fxch指令,以优化流水线。
· 在必要的时候展开循环,并且重新安排指令的顺序。
· 改变数据访问的模式,尽量使将要访问的数据位于缓存中。
· 提高并行度
Pentium II和Pentium III都只有一个被流水线化的浮点单元,合理的计算流程可以提高并行度。这样,你必须知道哪些指令是被流水线化的,一些常用指令的延迟是多少!请看下面的语句:
A = B + C + D;
E = F + G + H;
使用浮点指令可以实现的最简单的算法1是:
fld B; fadd C; fadd D; fstp A; fld F; fadd G; fadd H; fstp E;
在上面的算法中几乎每一条指令都依赖于前一指令的计算结果,这将使流水线时断时续。再来看看下面的算法2:
fld B; fadd C; fld F; fadd G; fxch st(1); fadd D; fld H; faddp st(2), st; fstp A; fstp E;
上面的代码使用了fxch指令,这是在浮点优化中很重要的一条指令。fadd的延迟是三个时钟周期,上面的指令序列几乎避开了每一个延迟。
在双核的E2160 1.8G机器上测试1亿次指令,算法1的时间为6469,算法2的时间为6750.
· fxch指令
在Pentium II和Pentium III处理器上,fxch指令的执行没有附加的时钟周期的消耗。你可以用它来访问堆栈深处的元素,这使浮点堆栈的使用更加灵活。
· 循环展开
把循环展开有以下好处:
· 减少了跳转的频率,这样,跳转的代价就不那么明显了。
· 可以统一编码,充分利用空闲的寄存器,提高运算速度。
· 更好地安排指令,减少其相关性,有更大的空间去优化流水线的设计,更容易安排指令,使之适应解码和预取的要求。
循环展开并不是仅仅减少了几个指令,要是想获得高性能,你有必要重新设计算法,尽可能去利用更多的资源。
· 浮点指令的延迟
很多浮点指令都有不止一个时钟周期的延迟,但是,由于Pentium II和 Pentium III有乱序执行的能力,这些延迟并不一定会很明显。但是,如果一条指令具有很长的延迟,我们就要重点考虑了。下面就来具体讨论一些指令的延迟,及其解决的方法。
· 浮点存储的延迟
一条浮点指令的存储操作必须付出一个额外的时钟周期去等待它的操作数。在fld之后,fst必须等待一个时钟周期;象fmul,fadd这样的指令,通常是有三个时钟周期的延迟,而它们后面的fst操作必须等待一个附加的时钟周期,也就是说它将忍受四个时钟周期的延迟。请看下面的例子:
· ; Store is dependent on the previous load.
· fld meml ; 1 fld takes 1 clock
· ; 2 fst waits, schedule something here
· fst mem2 ; 3,4 fst takes 2 clocks
· fadd meml ; 1 add takes 3 clocks
· ; 2 add, schedule something here
· ; 3 add, schedule something here
· ; 4 fst waits, schedule something here
· fst mem2 ; 5,2 fst takes 2 clocks
·
· ; Store is not dependent on the previous load:
· fld meml ; 1
· fld mem2 ; 2
· fxch st(l); 2
· fst mem3 ; 3 stores values loaded from meml
· ; A register may be used immediately after it has
· ; been loaded (with FLD):
· fld mem1 ; l
· fadd mem2 ; 2,3,4
· 计算的延迟
一些常用的指令,如fadd, fsub, fmul等,都有三个时钟周期的延迟,如果你想使用它的计算结果,就至少要在它后面插入两条指令。
对于一些具有较长时钟周期延迟的指令如fdiv, fsqrt等指令,就要考虑在它们后面插入整数指令。而且也应当考虑尽量减少这类指令的使用,似乎在任何处理器中,浮点除法都是极其耗费时间的,AMD甚至要使用迭代的方法来计算除法和平方根。
· 整数及浮点乘法
整数的乘法操作如:mul, imul,它们都是在浮点单元中执行的,所以,它们不能和浮点指令并行计算。
尽管浮点乘法的吞吐量为一个时钟周期,但是,fmul每两个时钟周期才能执行一次,如果你将两个fmul写在一起,你将免费获得一个时钟周期的延迟。注意,如果写成fmul/fxch/fmul的形式,效果也是一样的。
· 浮点单元的整数运算
尽量避免一些带有整数参数的浮点指令如:fiadd, fisub。应该把它们分解成两步来进行:一条fild加上一条相应的浮点指令。fiadd等指令将霸占流水线四个时钟周期,而替换以后却只需占用两个时钟周期。