c# - 一起计算sin和cos的最快方法是什么?

我想一起计算一个值的正弦和正弦值(例如创建一个旋转矩阵)。 当然我可以像sincos一样一个接一个地计算它们,但我想知道在需要两个值时是否有更快的方法。

编辑:总结到目前为止的答案:

  • 弗拉德说,有asm命令sincos计算它们(几乎与单独调用sincosf的时间相同)

  • 像Chi注意到的那样,这种优化有时已经由编译器完成(当使用优化标志时)。

  • caf指出,功能sincossincosf可能是可用的,只需包括math.h即可直接调用

  • 讨论使用查找表的tanascius方法存在争议。 (但是在我的计算机和基准测试场景中,它运行速度比sincos快3倍,而32位浮点的精度几乎相同。)

  • Joel Goodwin与一种非常快速逼近技术的有趣方法相关联,具有非常好的准确性(对我来说,这比查表更快)

Danvil asked 2019-08-13T03:54:44Z
18个解决方案
49 votes

现代Intel / AMD处理器具有指令FSINCOS,用于同时计算正弦和余弦函数。 如果您需要强大的优化,也许您应该使用它。

这是一个小例子:[http://home.broadpark.no/~alein/fsincos.html]

这是另一个例子(对于MSVC):[http://www.codeguru.com/forum/showthread.php?t=328669]

这是另一个例子(使用gcc):[http://www.allegro.cc/forums/thread/588470]

希望其中一人有所帮助。(我自己没有使用这个说明,抱歉。)

由于它们在处理器级别上受支持,我希望它们比表查找快得多。

编辑:
维基百科建议在387处理器上添加FSINCOS,因此您很难找到不支持它的处理器。

编辑:
英特尔的文档指出,FSINCOSFDIV(即浮点除法)慢约5倍。

编辑:
请注意,并非所有现代编译器都将正弦和余弦的计算优化为对FSINCOS的调用。特别是,我的VS 2008并没有这样做。

编辑:
第一个示例链接已死,但Wayback Machine仍有一个版本。

Vlad answered 2019-08-13T03:56:10Z
37 votes

现代x86处理器有一个fsincos指令,可以完全按照你的要求进行操作 - 同时计算sin和cos。 一个好的优化编译器应检测为相同值计算sin和cos的代码,并使用fsincos命令执行此操作。

为此,需要花费一些编译器标志,但是:

$ gcc --version
i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5488)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cat main.c
#include <math.h> 

struct Sin_cos {double sin; double cos;};

struct Sin_cos fsincos(double val) {
  struct Sin_cos r;
  r.sin = sin(val);
  r.cos = cos(val);
  return r;
}

$ gcc -c -S -O3 -ffast-math -mfpmath=387 main.c -o main.s

$ cat main.s
    .text
    .align 4,0x90
.globl _fsincos
_fsincos:
    pushl   %ebp
    movl    %esp, %ebp
    fldl    12(%ebp)
    fsincos
    movl    8(%ebp), %eax
    fstpl   8(%eax)
    fstpl   (%eax)
    leave
    ret $4
    .subsections_via_symbols

Tada,它使用fsincos指令!

Chi answered 2019-08-13T03:56:49Z
13 votes

当您需要性能时,可以使用预先计算的sin / cos表(一个表将执行,存储为字典)。 嗯,这取决于你需要的准确性(也许表会很大),但它应该非常快。

tanascius answered 2019-08-13T03:57:15Z
13 votes

从技术上讲,你可以通过使用复数和欧拉公式来实现这一目标。 因此,像(C ++)

complex<double> res = exp(complex<double>(0, x));
// or equivalent
complex<double> res = polar<double>(1, x);
double sin_x = res.imag();
double cos_x = res.real();

应该一步给你正弦和余弦。 如何在内部完成这是一个使用的编译器和库的问题。 它可能(并且可能)以这种方式花费更长的时间(因为Euler公式主要用于使用sincos来计算复数<complex> - 而不是相反)但可能有一些理论上的优化可能。


编辑

GNU C ++ 4.2的<complex>中的标题在polar内部使用sincos的显式计算,因此除非编译器具有一定的魔力,否则它对于优化看起来不太好(参见Chi的答案中所述的-ffast-math-mfpmath开关)。

Debilski answered 2019-08-13T03:57:59Z
12 votes

您可以计算任何一个,然后使用标识:

2 = 1  -  sin(x)2

但正如@tanascius所说,预先计算好的表是可行的方法。

Mitch Wheat answered 2019-08-13T03:58:39Z
7 votes

如果您使用GNU C库,那么您可以:

#define _GNU_SOURCE
#include <math.h>

并且您将获得sincos(),sincosf()sincosl()函数的声明,这两个函数一起计算两个值 - 可能是目标体系结构的最快方式。

caf answered 2019-08-13T03:59:14Z
7 votes

如caf所示,许多C数学库已经有了sincos()。 值得注意的例外是MSVC。

  • 自至少1987年以来,Sun已经拥有了sincos(二十三年;我有一个硬拷贝手册页)
  • HPUX 11在1997年有它(但不是HPUX 10.20)
  • 在2.1版(1999年2月)中添加到glibc
  • 成为gcc 3.4(2004),__ builtin_sincos()的内置。

关于查找,Eric S. Raymond在“Unix编程艺术”(2004)(第12章)中明确指出这是一个坏主意(目前时刻):

&#34;另一个例子是预先计算小表 - 例如,一个表   sin(x)用于优化3D图形引擎旋转的度数   在现代机器上采用365×4字节。 在处理器得到足够之前   要求缓存比内存更快,这是一个明显的速度   优化。 现在,每次重新计算可能会更快   而不是支付由此引起的额外缓存未命中的百分比    表。

&#34;但是在未来,当缓存变得更大时,这可能会再次出现。    更一般地说,许多优化都是暂时的,很容易转变    随着成本比率的变化而陷入悲观情绪。 知道的唯一方法是    衡量和看到。&#34; (来自Unix编程的艺术)

但是,从上面的讨论来看,并非所有人都同意。

Joseph Quinsey answered 2019-08-13T04:00:40Z
7 votes

这个论坛页面上有非常有趣的东西,它专注于找到快速的好近似值:[http://www.devmaster.net/forums/showthread.php?t=5784]

免责声明:我自己没有使用任何这些东西。

更新于2018年2月22日:Wayback Machine是现在访问原始页面的唯一途径:[https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast-and-accurate - 正弦余弦]

Joel Goodwin answered 2019-08-13T04:01:22Z
5 votes

我不相信查找表对于这个问题一定是个好主意。 除非您的准确度要求非常低,否则表格必须非常大。 现代CPU可以在从主存储器中获取值时进行大量计算。 这不是可以通过论证(甚至不是我的),测试和测量并考虑数据来正确回答的那些问题之一。

但我会看一下你在像AMD的ACML和英特尔的MKL这样的库中找到的SinCos的快速实现。

High Performance Mark answered 2019-08-13T04:01:58Z
3 votes

如果您愿意使用商业产品,并同时计算多个sin / cos计算(因此您可以使用向量函数),您应该查看英特尔的数学核心库。

它具有sincos功能

根据该文档,在高精度模式下,它在核心2 duo上平均为13.08个时钟/元素,我认为这将比fsincos更快。

Chi answered 2019-08-13T04:02:40Z
3 votes

本文介绍如何构造一个生成正弦和余弦的抛物线算法:

DSP技巧:Sin和Cos的同时抛物线逼近

[http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos]

Probes answered 2019-08-13T04:03:22Z
2 votes

当性能对于这种事情至关重要时,引入查找表并不罕见。

Tom Cabanski answered 2019-08-13T04:03:50Z
2 votes

对于一种创造性的方法,如何扩展泰勒系列? 由于他们有类似的术语,你可以做类似下面的伪:

numerator = x
denominator = 1
sine = x
cosine = 1
op = -1
fact = 1

while (not enough precision) {
    fact++
    denominator *= fact
    numerator *= x

    cosine += op * numerator / denominator

    fact++
    denominator *= fact
    numerator *= x

    sine += op * numerator / denominator

    op *= -1
}

这意味着你做这样的事情:从x和1开始为sin和余弦,按照模式 - 减去x ^ 2/2! 从余弦中减去x ^ 3/3! 从正弦,添加x ^ 4/4! 余弦,添加x ^ 5/5! 正弦......

我不知道这是否会有效。 如果你需要的精度低于内置的sin()和cos()给你的精度,那么它可能是一个选项。

Tesserex answered 2019-08-13T04:04:32Z
2 votes

在CEPHES库中有一个很好的解决方案可以非常快,你可以非常灵活地添加/删除准确性,以获得更多/更少的CPU时间。

请记住,cos(x)和sin(x)是exp(ix)的实部和虚部。 所以我们想要计算exp(ix)来得到两者。 我们预先计算exp(iy)的y在0到2pi之间的一些离散值。 我们将x移到区间[0,2pi]。 然后我们选择最接近x的y并写入
EXP(ⅸ)= EXP(IY+(IX-IY))= EXP(IY)EXP(I(X-Y))。

我们从查找表中得到exp(iy)。 并且因为| x-y | 如果很小(至多是y值之间距离的一半),泰勒级数只会在几个项中很好地收敛,所以我们将它用于exp(i(x-y))。 然后我们只需要一个复数乘法来得到exp(ix)。

另一个不错的属性是你可以使用SSE对其进行矢量化。

Jsl answered 2019-08-13T04:05:30Z
2 votes

您可能需要查看[http://gruntthepeon.free.fr/ssemath/,],它提供了一个源自CEPHES库的SSE矢量化实现。它具有良好的准确性(最大偏离sin / cos大约5e-8)和速度(在单个呼叫的基础上稍微优于fsincos,并且在多个值上明显胜出)。

SleuthEye answered 2019-08-13T04:05:58Z
1 votes

我发布了一个涉及内联ARM组件的解决方案,能够同时计算两个角度的正弦和余弦:ARMv7 + NEON的快速正弦/余弦

jcayzac answered 2019-08-13T04:06:25Z
1 votes

在javascript中同时准确但快速地逼近sin和cos函数,可以在这里找到:[http://danisraelmalta.github.io/Fmath/](轻松导入到c / c ++)

user2781980 answered 2019-08-13T04:06:53Z
0 votes

你有没有想过为两个函数声明查找表? 你仍然需要&#34;计算&#34; sin(x)和cos(x),但如果你不需要高度的准确性,它肯定会更快。

Frank Shearar answered 2019-08-13T04:07:22Z
translate from https://stackoverflow.com:/questions/2683588/what-is-the-fastest-way-to-compute-sin-and-cos-together