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

Dan*_*vil 98 c c# c++ algorithm math

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

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

  • 弗拉德说,有asm命令FSINCOS计算它们(几乎与FSIN单独呼叫同时)

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

  • 咖啡厅指出,其功能sincossincosf可能是可用的,并且可以通过只包括直接调用math.h

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

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

Vla*_*lad 51

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

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

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

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

编辑:
英特尔的文档说明FSINCOS比慢FDIV(即浮点除法)慢约5倍.

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

编辑:
第一个示例链接已死,但Wayback机器上仍有一个版本.

  • `fsincos`指令*不是"非常快".英特尔自己的优化手册称其在最近的微架构上需要119到250个周期.相比之下,英特尔的数学库(与ICC一起分发)可以在不到100个周期内单独*计算`sin`和`cos`,使用的是使用SSE而不是x87单元的软件实现.同时计算两者的类似软件实现可能更快. (12认同)
  • 另请注意,`fsincos`本身并不是一个完整的实现; 您需要一个额外的范围缩减步骤,以将参数放入`fsincos`指令的有效输入范围.库`sin`和`cos`函数包括这种减少以及核心计算,因此它们比我列出的循环时间更快(相比之下). (4认同)
  • @Vlad:ICC数学库不是开源的,我没有许可重新发布它们,所以我不能发布程序集.我可以告诉你,他们没有内置的"罪"计算来利用它们; 他们使用与其他人相同的SSE指令.对于你的第二个评论,相对于`fdiv`的速度是无关紧要的; 如果有两种方法可以做某事,一种方法的速度是另一种方式的两倍,那么无论相对于一些完全不相关的任务需要多长时间,将速度较慢的方法称为"快速"是没有意义的. (2认同)

Chi*_*Chi 38

现代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
Run Code Online (Sandbox Code Playgroud)

Tada,它使用fsincos指令!

  • mfpmath = 387将强制gcc使用x87指令而不是SSE指令.我怀疑MSVC有类似的优化和标志,但我没有MSVC方便确定.使用x87指令可能会对其他代码中的性能造成不利影响,但您还应该看看我的其他答案,使用英特尔的MKL. (3认同)

tan*_*ius 13

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

  • 预先计算的表几乎肯定会比调用`sin`慢,因为预先计算的表会丢弃缓存. (11认同)

Deb*_*ski 13

从技术上讲,你可以通过使用复数和欧拉公式来实现这一目标.因此,像(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();
Run Code Online (Sandbox Code Playgroud)

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


编辑

在标题<complex>为GNU C++ 4.2使用的明确的计算sincos内部polar,所以除非编译器做了一些魔术(见它不查找的优化有太好-ffast-math-mfpmath开关写在驰的答案).


Mit*_*eat 12

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

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

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

  • 注意,√(1-cos ^ 2 x)不如直接计算sin x精确,特别是当x~0时. (12认同)
  • 不要忘记找到正确的操作标志. (9认同)
  • 请注意,使用此方法涉及计算功率和平方根,因此如果性能很重要,请确保验证这实际上比直接计算其他trig函数更快. (8认同)
  • `sqrt()`通常在硬件中进行优化,所以它可能比`sin()`或`cos()`更快.权力只是自我倍增,所以不要使用`pow()`.如果没有硬件支持,有一些技巧可以非常快速地获得相当准确的平方根.最后,在执行任何此操作之前,请务必进行配置. (4认同)

Joe*_*win 8

这个论坛页面上有非常有趣的东西,专注于找到快速的好近似值: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-正弦,余弦


caf*_*caf 8

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

#define _GNU_SOURCE
#include <math.h>
Run Code Online (Sandbox Code Playgroud)

你会得到的声明sincos(),sincosf()sincosl()在你的目标体系以最快的方式可能-函数计算两个值在一起.


Jos*_*sey 7

如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章)中明确指出这是一个坏主意(目前时刻):

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

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

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

  • "365 x 4字节".你需要考虑闰年,所以实际应该是365.25 x 4字节.或许他的意思是使用圆圈中的度数而不是地球年份的天数. (10认同)

Hig*_*ark 5

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

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