主要问题
有人对x64有快速的sin()实现吗? 它不需要是纯粹的pascal。
解释
我有一个VCL应用程序,在某些情况下,当它为x64编译时运行速度慢很多。
它进行了大量的浮点3d计算,并且我已经跟踪了这一事实,当输入值变大时,System.Sin()和System.Cos()在x64上要慢很多
我通过创建一个简单的测试应用程序来计算它,该应用程序测量计算sin(x)
需要多长时间,x的值不同,差异很大:
call: x64: x86:
Sin(1) 16 ms 20 ms
Sin(10) 30 ms 20 ms
Sin(100) 32 ms 20 ms
Sin(1000) 34 ms 21 ms
Sin(10000) 30 ms 21 ms
Sin(100000) 30 ms 16 ms
Sin(1000000) 35 ms 20 ms
Sin(10000000) 581 ms 20 ms
Sin(100000000) 1026 ms 21 ms
Sin(1000000000) 1187 ms 22 ms
Sin(10000000000) 1320 ms 21 ms
Sin(100000000000) 1456 ms 20 ms
Sin(1000000000000) 1581 ms 17 ms
Sin(10000000000000) 1717 ms 22 ms
Sin(100000000000000) 1846 ms 23 ms
Sin(1E15) 1981 ms 21 ms
Sin(1E16) 2100 ms 21 ms
Sin(1E17) 2240 ms 22 ms
Sin(1E18) 2372 ms 18 ms
etc etc etc
您在此处看到的是sin(1E5)
的运行速度是sin(1E8)
的300倍。
如果您有兴趣,我已经创建了上表:
{$APPTYPE CONSOLE}
program SinTest;
uses Diagnostics, Math, SysUtils;
var
i : Integer;
x : double;
sw: TStopwatch;
begin
x := 1;
while X < 1E18 do
begin
sw := TStopwatch.StartNew;
for i := 1 to 500000 do
System.Sin(x);
// WriteLn(System.sin(x), #9,System.Sin(fmod(x,2*pi)));
sw.Stop;
WriteLn(' ', ('Sin(' + round(x).ToString + ')'):20, ' ', sw.ElapsedMilliseconds,' ms');
x := x * 10;
end;
WriteLn('Press any key to continue');
readln;
end.
注意:
关于更快的正弦函数,StackOverflow上有一些问题,但它们都没有源代码可以移植到Delphi,如下所示:Fastest implementation of sine, cosine and square root in C++ (doesn't need to be much accurate)
x64的其余部分运行速度超过其32位对应
通过这样做,我发现了一些糟糕的解决方法:
Sin(FMod(x,2*pi))
。它提供了正确的结果,并且对于更大的数字它可以快速运行。对于较小的数字,它当然有点慢。
答案 0 :(得分:3)
虽然在用户模式代码中可能会强烈反对这一点(并且在内核模式代码中完全禁止),但如果您 希望在x64代码中保留旧版x87行为,那么< em>可以编写这样的函数:
function SinX87(x:double):double;
var
d : double;
asm
movsd qword ptr [rbp+8], xmm0
fld qword ptr [rbp+8]
fsin
fstp qword ptr [rbp+8]
movsd xmm0, qword ptr [rbp+8]
end;
这增加了一些开销,因为您必须将SSE寄存器中的值弹出到堆栈中,将其加载到x87单元中,执行计算,将值弹回堆栈,然后将其加载回XMM0表示功能结果。但是sin
计算非常重,所以这是一个相对较小的开销。如果您需要保留x87 sin
实现的whatever idiosyncracies,我只会这样做。
存在其他库,在x64代码中比Delphi的purepascal例程更有效地计算sin
。我压倒性的偏好是将一组好的C ++例程导出到DLL中。此外,正如大卫所说,无论如何,使用具有可笑大量参数的trig函数并不是一件明智的事情。
答案 1 :(得分:2)
如果您对我的最终解决方案感兴趣:
我做了一点实验(如LU RD和e)。 - 杰里科芬建议):
function sin(x:double):double;
begin
if x<1E6 then
Result := system.sin(x)
else
Result := system.sin(fmod(x,2*pi));
end;
可能它与我的特定CPU上的测试代码的可预测性有关,但是如果我没有执行if
,那么实际上计算的值越小,并且总是使用fmod() 。很奇怪,因为需要进行一些划分,我认为比比较两个值要慢。
所以这就是我现在最终使用的内容:
function sin(const x: double): double; { inline; }
begin
{$IFDEF CPUX64}
Result := System.sin(Math.FMod(x,2*pi));
{$ELSE}
Result := System.sin(x);
{$ENDIF}
end;
顺便添加inline
,它的运行速度提高了1.5倍。然后它在我的机器上运行与J ...的功能完全一样快。但即使没有Inline,这已经比System.Sin()快了几百倍,所以我就是这样做的。