更快的sin()for x64

时间:2016-04-04 20:14:27

标签: performance delphi 64-bit sin sine

主要问题

有人对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))。它提供了正确的结果,并且对于更大的数字它可以快速运行。对于较小的数字,它当然有点慢。

2 个答案:

答案 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()快了几百倍,所以我就是这样做的。