什么是一起计算罪和cos的最快方法?
我想同时计算一个值的正弦和同正弦(例如创build一个旋转matrix)。 当然,我可以像a = cos(x); b = sin(x);
那样分别计算它们a = cos(x); b = sin(x);
a = cos(x); b = sin(x);
,但我不知道是否有一个更快的方式,当需要这两个值。
编辑:总结到目前为止的答案:
-
弗拉德说,有一个命令
FSINCOS
的asm命令计算他们两个(几乎在同一时间呼叫FSIN
单独) -
像Chi注意到的那样,这个优化有时候已经被编译器完成了(当使用优化标志的时候)。
-
sincosf
指出,函数sincosf
和sincosf
可能是可用的,可以直接通过包括math.h
来调用 -
讨论使用查找表的tanascius方法是有争议的。 (然而,在我的电脑和基准testing场景中,它比
sincos
运行速度快3倍,对于32位浮点运算精度几乎相同。) -
乔尔·古德温(Joel Goodwin)把一个有趣的方法与一个相当不错的快速逼近技术联系起来(对于我来说,这个速度甚至比查表更快)
现代英特尔/ 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
希望其中一个帮助。 (我自己没有使用这个指令,对不起。)
由于它们在处理器级别上得到了支持,所以我期望它们比表查找要快得多。
编辑:
维基百科build议在387处理器上增加FSINCOS
,所以你几乎找不到一个不支持它的处理器。
编辑:
英特尔的文档指出, FSINCOS
比FDIV
(即浮点除法)慢大约5倍。
编辑:
请注意,并不是所有的现代编译器都可以通过调用FSINCOS
来优化正弦和余弦的计算。 特别是,我的VS 2008没有这样做。
编辑:
第一个例子链接已经死了,但在Wayback机器上仍然有一个版本 。
现代的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
多田,它使用fsincos指令!
当你需要表演的时候,你可以使用一个预先计算的罪恶/成本表(一个表格将被存储为一个词典)。 那么,这取决于你需要的准确性(也许桌子会很大),但它应该是非常快的。
从技术上讲,你可以通过使用复数和欧拉公式来实现 。 因此,像(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();
应该给你正弦和余弦一步。 内部如何完成是编译器和库被使用的问题。 它可能(也可能)需要更长的时间来这样做(只是因为欧拉公式主要用来计算使用sin
和cos
的复数exp
,而不是相反的方式),但是可能会有一些理论上的优化。
编辑
GNU C ++ 4.2的<complex>
的头文件使用了在polar
内的sin
和cos
显式计算,所以对于那里的优化来说它看起来不太好,除非编译器做了一些魔术(参见-ffast-math
和-mfpmath
开关写在Chi的答案 )。
你可以计算出来然后使用这个标识:
cos(x) 2 = 1 - sin(x) 2
但@tanascius说,预先计算的表格是要走的路。
如果你使用GNU C库,那么你可以这样做:
#define _GNU_SOURCE #include <math.h>
你将得到sincosf()
sincos()
, sincosf()
和sincosl()
函数的声明,这两个函数可以同时计算两个值 – 大概是以你的目标架构最快的方式。
许多Cmath库,正如caf所示,已经有了sincos()。 值得注意的例外是MSVC。
- Sun自从1987年(至less二十三年;我有一个硬拷贝手册页)以来就有sincos()
- HPUX 11在1997年(但不是在HPUX 10.20)
- 版本2.1(1999年2月)添加到glibc
- 成为gcc 3.4(2004)中的一个内置函数__builtin_sincos()。
关于查找,Eric S. Raymond在“ Unix程序devise艺术” (2004)(第12章)中明确表示这是一个糟糕的想法(在当前的时刻):
“另一个例子是预先计算小型表 – 例如,为了优化3Dgraphics引擎中的旋转,一个sin(x)表将在现代机器上花费365×4字节。在处理器获得足够快的内存来caching需求之前,这是一个明显的速度优化,现在每次重新计算可能会更快,而不是支付由表引起的额外caching未命中的百分比。
“但是在未来,随着caching规模的扩大,这种情况可能会再次出现,更为普遍的是,许多优化措施是暂时的,随着成本比率的变化,很容易变成悲观的现象,唯一的方法就是测量和观察。 (来自Unix编程艺术 )
但是,从上面的讨论来看,并不是每个人都同意的。
我不认为查找表对于这个问题是一个好主意。 除非您的准确度要求非常低,否则表格需要非常大。 而现代的CPU可以做大量的计算,而从主内存中获取一个值。 这不是那些可以通过论证(甚至是我的)不能正确回答的问题之一,testing和测量并考虑数据。
但是我期待在诸如AMD的ACML和Intel的MKL等库中find的SinCos的快速实现。
在这个论坛页面上有非常有趣的东西,其重点是find快速的近似值: http : //www.devmaster.net/forums/showthread.php? t= 5784
免责声明:我自己没有使用任何这些东西。
注意:链接的网站已经移到这里http://devmaster.net/posts/9648/fast-and-accurate-sine-cosine
本文展示了如何构build一个同时产生正弦和余弦的抛物线algorithm:
DSP技巧:Sin和Cos的同时抛物逼近
http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos
当性能对这种事情至关重要时,引入查找表并不罕见。
对于创造性的方法,如何扩展泰勒级数? 既然他们有类似的术语,你可以做一些类似下面的伪命令:
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开始为罪和余弦,按照模式 – 减去x ^ 2/2! 从余弦减去x ^ 3/3! 从正弦,加上x ^ 4/4! 余弦,加上x ^ 5/5! 正弦
我不知道这是否是高性能的。 如果你所需要的精度低于内build的sin()和cos()给你的精度,那么它可能是一个选项。
如果您愿意使用商业产品,并同时计算多个正弦/余弦计算(因此您可以使用vector函数),则应该查看英特尔的math核心函数库。
它有一个sincosfunction
根据这个文档,它在高精度模式下的核心2 duo平均为13.08个时钟/单元,我认为它比fsincos更快。
我已经发布了一个涉及内联ARM汇编的解决scheme,可以同时计算两个angular度的正弦和余弦: ARMv7 + NEON的快速正弦/余弦
在CEPHES库中有一个很好的解决scheme,它可以非常快速,并且可以相当灵活地添加/删除准确性,从而使CPU时间更less/更less。
请记住,cos(x)和sin(x)是exp(ix)的实部和虚部。 所以我们要计算exp(ix)来得到两者。 我们预先计算exp(iy)对于0到2pi之间的y的离散值。 我们将x移到区间[0,2pi)。 然后我们select最接近x的y并写入
EXP(ⅸ)= EXP(IY +(IX-IY))= EXP(IY)EXP(I(XY))。
我们从查找表中得到exp(iy)。 由于| xy | 是小的(至多是y值之间的距离的一半),泰勒级数将在几个项中很好地收敛,所以我们使用exp(i(xy))。 然后我们只需要一个复数乘法来得到exp(ix)。
另一个不错的特性是你可以使用SSE进行vector化。
在javascript中同时准确而快速地近似sin和cos函数,可以在这里find: http : //danisraelmalta.github.io/Fmath/ (很容易导入到c / c ++)
你可能想看看http://gruntthepeon.free.fr/ssemath/ ,它提供了一个由CEPHES库启发的SSE向量化实现。 它具有良好的准确性(与sin / cos的最大偏差在5e-8左右)和速度(在单个呼叫的基础上稍微优于fsincos,胜过多个值)。
你有没有想过为这两个函数声明查找表? 你仍然需要“计算”sin(x)和cos(x),但是如果你不需要很高的准确度,它肯定会更快。