x64 更快的 sin()

Faster sin() for x64

主要问题

有人有 x64 的快速 sin() 实现吗? 它不需要是纯帕斯卡。

说明

我有一个 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 倍。

如果你有兴趣,我已经创建了上面的 table 这样的:

{$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.

备注:

虽然这在用户模式代码中可能是非常不鼓励的(并且在内核模式代码中是完全禁止的),但如果您确实想要保留旧的 x87 行为你的 x64 代码你 可以 编写这样的函数 :

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 代码中计算 sin 比 Delphi 的 purepascal 例程更有效。在这里,我压倒性的偏好是将一组好的 C++ 例程导出到 DLL。此外,正如 David 所说,无论如何,使用带有大得离谱的参数的三角函数并不是一件明智的事情。

如果您对我的最终解决方案感兴趣:

我做了一些实验,通过这样做(如 LU RD 和 e)。 – Jerry Coffin 建议):

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()。 St运行ge,因为需要进行一些除法,我希望这比比较两个值要慢。

这就是我现在最终使用的:

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... 的功能一样快。但即使没有内联,这也已经比 System.Sin() 快数百倍,所以我要这样做。