最近无聊中一不留神又在网上看到了Quake中的那段著名的Inverse Square Root代码,忽然产生了一个冲动,将那段代码改成了一个计算浮点除法的函数,不幸就得到了一段比两个浮点数直接相除更快的代码。先说说我是怎么改造的,这段Quake代码计算的是某个浮点数的平方根的倒数,如果我们用这段代码对除数进行处理,得到的结果平方一下,再乘以被除数,那么结果当然就是两个数直接相除的值了。看上去很白痴的行为,可是结果却出人意料,绕这么一大圈来得到结果,比直接两个数相除还快。
简单测试了一下,又发现了另一件有趣的事情,在Intel Core Duo cpu上我的函数大约快50%,可是在Intel P4上我的函数竟然快了220%(很狼狈的结果,在两种cpu上我的函数执行时间几乎相同),看来传说中P4有时比P3还慢的谣言并不是瞎说。下面详细解说一下我的改造:先看一下传说中的那个Inverse sqrt函数的相当神奇的源码:
float InvSqrt(float x){
float xhalf = 0.5f * x;
int i = *(int*)&x; // store floating-point bits in integer
i = 0x5f3759d5 - (i >> 1); // initial guess for Newton's method
x = *(float*)&i; // convert new bits into float
x = x*(1.5f - xhalf*x*x); // One round of Newton's method
return x;
}改造第一步,当然是将其翻译成delphi:
function InvSqrt(x: Single): Single;
var
xhalf: Single;
i: Integer;
begin
xhalf := x * 0.5;
i := PInteger(@xhalf)^;
i := $5f3759d5 - (i shr 1);
x := PSingle(@i)^;
Result := x * (1.5 - xhalf*x*x);
end;由于本人对执行效率的变态追求(并不是一个好习惯),我又进一步将其改成了汇编:
var
OneHalf: DWORD = $3FC00000;function f_invsqrt(v: Single): Single;
asm
MOV EAX, DWORD PTR [EBP+8] // i := PInteger(@x)^;
SUB EAX, $800000
PUSH EAX // xhalf := x*2
ADD EAX, $800000
MOV EDX, $5F3759D5
SHR EAX, 1
SUB EDX, EAX // i := 5f3759d5-(i shr 1)
PUSH EDX
FLD DWORD PTR [ESP] // x := PSingle(@i)^
FLD ST(0)
FMUL ST(0), ST(0)
FMUL DWORD PTR [ESP+4]
FSUBR DWORD PTR OneHalf
FMULP // result := x * (1.5 - xhalf*x*x);
ADD ESP, 8
end;再将其改成除法, 用delphi写的话就是(当然,这段代码绝对比直接除慢得多的多的多):
function f_div(x, y: Single): Single;
begin
if y<0 then
begin
y := -y;
x := -x;
end;
result :=f_invsqrt(y) * f_invsqrt(y) * x;
end;最后优化后的代码如下:function f_div(v1, v2: Single): Single;
asm
/* 调整v1, v2的值: v2取绝对值并相应调整v1的正负 */
MOV EAX, DWORD PTR [EBP+8]
MOV EDX, $80000000
AND EDX, EAX
AND EAX, $7FFFFFFF
XOR DWORD PTR [EBP+12], EDX
FLD DWORD PTR [EBP+12]
/* 计算v2的Inverse sqrt再平方再乘以v1 */
SUB EAX, $800000
PUSH EAX
ADD EAX, $800000
MOV EDX, $5F3759D5
SHR EAX, 1
SUB EDX, EAX
PUSH EDX
FLD DWORD PTR [ESP]
FMUL ST(0), ST(0)
FMUL ST(1), ST(0)
FMUL DWORD PTR [ESP+4]
FSUBR DWORD PTR OneHalf
FMUL ST(0), ST(0)
FMULP
ADD ESP, 8
end;
简单测试了一下,又发现了另一件有趣的事情,在Intel Core Duo cpu上我的函数大约快50%,可是在Intel P4上我的函数竟然快了220%(很狼狈的结果,在两种cpu上我的函数执行时间几乎相同),看来传说中P4有时比P3还慢的谣言并不是瞎说。下面详细解说一下我的改造:先看一下传说中的那个Inverse sqrt函数的相当神奇的源码:
float InvSqrt(float x){
float xhalf = 0.5f * x;
int i = *(int*)&x; // store floating-point bits in integer
i = 0x5f3759d5 - (i >> 1); // initial guess for Newton's method
x = *(float*)&i; // convert new bits into float
x = x*(1.5f - xhalf*x*x); // One round of Newton's method
return x;
}改造第一步,当然是将其翻译成delphi:
function InvSqrt(x: Single): Single;
var
xhalf: Single;
i: Integer;
begin
xhalf := x * 0.5;
i := PInteger(@xhalf)^;
i := $5f3759d5 - (i shr 1);
x := PSingle(@i)^;
Result := x * (1.5 - xhalf*x*x);
end;由于本人对执行效率的变态追求(并不是一个好习惯),我又进一步将其改成了汇编:
var
OneHalf: DWORD = $3FC00000;function f_invsqrt(v: Single): Single;
asm
MOV EAX, DWORD PTR [EBP+8] // i := PInteger(@x)^;
SUB EAX, $800000
PUSH EAX // xhalf := x*2
ADD EAX, $800000
MOV EDX, $5F3759D5
SHR EAX, 1
SUB EDX, EAX // i := 5f3759d5-(i shr 1)
PUSH EDX
FLD DWORD PTR [ESP] // x := PSingle(@i)^
FLD ST(0)
FMUL ST(0), ST(0)
FMUL DWORD PTR [ESP+4]
FSUBR DWORD PTR OneHalf
FMULP // result := x * (1.5 - xhalf*x*x);
ADD ESP, 8
end;再将其改成除法, 用delphi写的话就是(当然,这段代码绝对比直接除慢得多的多的多):
function f_div(x, y: Single): Single;
begin
if y<0 then
begin
y := -y;
x := -x;
end;
result :=f_invsqrt(y) * f_invsqrt(y) * x;
end;最后优化后的代码如下:function f_div(v1, v2: Single): Single;
asm
/* 调整v1, v2的值: v2取绝对值并相应调整v1的正负 */
MOV EAX, DWORD PTR [EBP+8]
MOV EDX, $80000000
AND EDX, EAX
AND EAX, $7FFFFFFF
XOR DWORD PTR [EBP+12], EDX
FLD DWORD PTR [EBP+12]
/* 计算v2的Inverse sqrt再平方再乘以v1 */
SUB EAX, $800000
PUSH EAX
ADD EAX, $800000
MOV EDX, $5F3759D5
SHR EAX, 1
SUB EDX, EAX
PUSH EDX
FLD DWORD PTR [ESP]
FMUL ST(0), ST(0)
FMUL ST(1), ST(0)
FMUL DWORD PTR [ESP+4]
FSUBR DWORD PTR OneHalf
FMUL ST(0), ST(0)
FMULP
ADD ESP, 8
end;
2. 性能没你说的那么好,在Pentium4上比使用fdiv指令快约28%,在PentiumM上快约37%。
3. 比使用divss指令要慢。贡献两个函数:// the FDIV one
function f_div_fpu(v1, v2: single): single;
asm
fld dword ptr [v1]
fdiv dword ptr [v2]
end;// for PentiumIII+ processor
function f_div_sse(v1, v2: single): single;
asm
movd xmm1,[v1]
divss xmm1,[v2]
movd [v1],xmm1
fld dword ptr [v1]
end;
做到跟sqrt一样的效率可惜,至今没人发现它。
如果你对c++代码做汇编翻译。执行速度估计会比你的代码还要快。
原因在于,你绕过了编译器。编译器生成的汇编代码是通用优化。你手工生成的汇编代码是专用优化。所以快。
并不说明你的算法快。
算法快必定有算法快的原因与证明推理。
///
p4流水线超长。主频超高。能力超低。因为它只对简单指今操行速度快。复杂指今就over了。
现在先进的cpu都是擅长处理复杂指今。