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

时间:2010-04-21 14:05:53

标签: c# c++ c algorithm math

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

修改 总结到目前为止的答案:

  • Vlad说,有asm命令FSINCOS计算它们(几乎与单独调用FSIN一样)

    < / LI>
  • Chi注意到,此优化有时已由编译器完成(使用优化标志时)。

  • caf指出,函数sincossincosf可能是可用的,只需包含math.h

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

  • Joel Goodwin与极端快速近似技术的有趣方法相关联,具有相当好的准确性(对我来说,这比查表更快)

19 个答案:

答案 0 :(得分:49)

现代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

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

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

编辑:
Wikipedia表示在387个处理器上添加了FSINCOS,因此您很难找到不支持它的处理器。

编辑:
Intel's documentation指出FSINCOSFDIV慢约5倍(即浮点除法)。

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

编辑:
第一个示例链接已死,但有still a version at the Wayback Machine

答案 1 :(得分:37)

现代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指令!

答案 2 :(得分:13)

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

答案 3 :(得分:13)

从技术上讲,您可以通过使用复数和Euler’s Formula来实现此目的。因此,像(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的公式主要用于使用expsin来计算复杂cos - 而不是相反)但可能会有一些理论上的优化。


修改

GNU C ++ 4.2 <complex>中的标题使用sin内的cospolar的显式计算,因此对于那里的优化看起来不太好,除非编译器做了一些魔术(参见Chi’s answer中所写的-ffast-math-mfpmath开关。)

答案 4 :(得分:12)

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

cos(x)2 = 1 - sin(x)2

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

答案 5 :(得分:7)

此论坛页面上有非常有趣的内容,主要是找到快速的好近似值: 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-sine-cosine

答案 6 :(得分:7)

如果你使用GNU C库,那么你可以这样做:

#define _GNU_SOURCE
#include <math.h>

您将获得同时计算这两个值的sincos()sincosf()sincosl()函数的声明 - 可能是您目标架构的最快方式。

答案 7 :(得分:7)

很多C数学库,如caf所示,已经有了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章)中明确表示这是一个坏主意(目前时刻):

  

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

     

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

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

答案 8 :(得分:5)

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

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

答案 9 :(得分:3)

如果您愿意使用商业产品,并且同时计算多项罪恶/费用计算(因此您可以使用向量函数),您应该查看Intel's Math Kernel Library.

它有sincos function

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

答案 10 :(得分:3)

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

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

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

答案 11 :(得分:2)

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

答案 12 :(得分:2)

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

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()给你的精度,那么它可能是一个选项。

答案 13 :(得分:2)

在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对其进行矢量化。

答案 14 :(得分:2)

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

答案 15 :(得分:1)

我发布了一个涉及内联ARM组件的解决方案,能够一次计算两个角度的正弦和余弦:Fast sine/cosine for ARMv7+NEON

答案 16 :(得分:1)

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

答案 17 :(得分:0)

您是否考虑为这两个函数声明查找表?你仍然需要“计算”sin(x)和cos(x),但是如果你不需要高精度的话,它肯定会更快。

答案 18 :(得分:0)

MSVC编译器可能会使用(内部)函数

 ___libm_sse2_sincos_ (for x86)
 __libm_sse2_sincos_  (for x64)
如果指定了适当的编译器标记,则在优化的版本中

(至少/ O2 / arch:SSE2 / fp:fast)。这些函数的名称似乎暗示着它们并没有计算出单独的sin和cos,而是“一步一步”计算出的。

例如:

void sincos(double const x, double & s, double & c)
{
  s = std::sin(x);
  c = std::cos(x);
}

使用/ fp:fast组装(对于x86):

movsd   xmm0, QWORD PTR _x$[esp-4]
call    ___libm_sse2_sincos_
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
mov     eax, DWORD PTR _c$[esp-4]
shufpd  xmm0, xmm0, 1
movsd   QWORD PTR [eax], xmm0
ret     0

不带/ fp:fast但带/ fp:precise(这是默认设置)的汇编程序(对于x86)调用单独的sin和cos:

movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_sin_precise
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_cos_precise
mov     eax, DWORD PTR _c$[esp-4]
movsd   QWORD PTR [eax], xmm0
ret     0

因此,/ fp:fast是sincos优化所必需的。

但请注意

___libm_sse2_sincos_

可能不如

__libm_sse2_sin_precise
__libm_sse2_cos_precise

由于名称末尾缺少“精确”字样。