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.
备注:
Whosebug 上有一些关于更快的正弦函数的问题,但是 none 有源代码可以移植到 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))
。它提供了正确的结果,并且对于较大的数字运行速度很快。对于较小的数字,它当然会慢一点。
虽然这在用户模式代码中可能是非常不鼓励的(并且在内核模式代码中是完全禁止的),但如果您确实想要保留旧的 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() 快数百倍,所以我要这样做。
主要问题
有人有 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.
备注:
Whosebug 上有一些关于更快的正弦函数的问题,但是 none 有源代码可以移植到 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))
。它提供了正确的结果,并且对于较大的数字运行速度很快。对于较小的数字,它当然会慢一点。
虽然这在用户模式代码中可能是非常不鼓励的(并且在内核模式代码中是完全禁止的),但如果您确实想要保留旧的 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() 快数百倍,所以我要这样做。