快速实现c ++的三角函数

时间:2011-04-25 09:43:31

标签: c++ math optimization

简短版本:我想知道标准三角函数的实现是否比math.h中包含的更快。

长版本:我有一个在数字上非常繁重的程序(这是一个物理模拟),需要调用三角函数,主要是sincos。目前我只是使用math.h中包含的实现。分析表明,对这些函数的调用成本比我预期的要高(希望)。

虽然代码的其他部分肯定有足够的优化空间,但速度更快sincos可能会给我一些额外的百分比..所以,你们有什么建议吗?
在另一个post中,建议使用自制的查找表。但也许有其他选择?或者在一些图书馆中提供现成的和经过良好测试的查找解决方案?

9 个答案:

答案 0 :(得分:17)

以下是有关如何对三角函数进行幂级数近似(不是泰勒级数)的一些很好的幻灯片:http://www.research.scea.com/gdc2003/fast-math-functions.html

它面向游戏程序员,这意味着精度会因性能而牺牲,但您应该能够在近似中添加另一个或两个术语以获得一些准确性。

关于这一点的好处是你也应该能够轻松地将它扩展到SIMD,这样你就可以在一个计算4个值的sin或cos(如果你使用双精度则为2)。

希望有帮助...

答案 1 :(得分:7)

如果你可以进一步优化它,那应该非常快,请像在pastie.org或其他地方一样发布代码。

计算机规格 - > 512MB Ram,Visual Studio 2010,Windows XP Professional SP3版本2002,Intel(R)Pentium(R)4 CPU 2.8GHZ。

这非常准确,实际上在某些情况下会提供稍微好一点的结果。例如。 C ++中的90,180,270度返回非0小数。

0到359度的完整表:https://pastee.org/dhwbj

格式 - >学位# - > MINE_X(#),CosX(#),MINE_Z(#),SinZ(#)。

以下是用于构建上述表格的代码。如果使用更大的数据类型,则可以使其更准确。我使用了unsigned short并做了N / 64000。那么最接近我的cos(##)和sin(##)舍入到那个索引。我也尝试使用尽可能少的额外数据,因此这不会是一些杂乱的表,其中包含cos和sin的720浮点值。哪个可能会产生更好的结果,但完全浪费内存。下面的表格尽可能小。我想看看是否可以制作一个可以舍入所有这些短值并使用它的方程式。我不确定它是否会更快,但它会彻底消除表格,并且可能不会降低任何速度或更多。

因此,与C ++ cos / sin操作相比,准确率为99.99998%到100%。

下面是用于计算cos / sin值的表。

static const unsigned __int16 DEGREE_LOOKUP_TABLE[91] =
{
    64000, 63990, 63961, 63912, 63844, 63756,
    63649, 63523, 63377, 63212, 63028, 62824,
    62601, 62360, 62099, 61819, 61521, 61204,
    60868, 60513, 60140, 59749, 59340, 58912,
    58467, 58004, 57523, 57024, 56509, 55976,
    55426, 54859, 54275, 53675, 53058, 52426,
    51777, 51113, 50433, 49737, 49027, 48301,
    47561, 46807, 46038, 45255, 44458, 43648,
    42824, 41988, 41138, 40277, 39402, 38516,
    37618, 36709, 35788, 34857, 33915, 32962,
    32000, 31028, 30046, 29055, 28056, 27048,
    26031, 25007, 23975, 22936, 21889, 20836,
    19777, 18712, 17641, 16564, 15483, 14397,
    13306, 12212, 11113, 10012,  8907,  7800,
     6690,  5578,  4464,  3350,  2234,  1117,
        0,
};

下面是执行cos / sin计算的实际代码。

    int deg1 = (int)degrees;
    int deg2 = 90 - deg1;
    float module = degrees - deg1;
    double vX = DEGREE_LOOKUP_TABLE[deg1] * 0.000015625;
    double vZ = DEGREE_LOOKUP_TABLE[deg2] * 0.000015625;
    double mX = DEGREE_LOOKUP_TABLE[deg1 + 1] * 0.000015625;
    double mZ = DEGREE_LOOKUP_TABLE[deg2 - 1] * 0.000015625;
    float vectorX = vX + (mX - vX) * module;
    float vectorZ = vZ + (mZ - vZ) * module;
    if (quadrant & 1)
    {
        float tmp = vectorX;
        if (quadrant == 1)
        {
            vectorX = -vectorZ;
            vectorZ = tmp;
        } else {
            vectorX = vectorZ;
            vectorZ = -tmp;
        }
    } else if (quadrant == 2) {
        vectorX = -vectorX;
        vectorZ = -vectorZ;
    }

下面的速度使用最初提到的计算机规格。在调试模式之前,我在调试模式下运行它,但运行的是我认为没有调试的调试可执行文件。

我的方法

1,000 Iterations -> 0.004641 MS or 4641 NanoSeconds.
100,000 Iterations -> 4.4328 MS.
100,000,000 Iterations -> 454.079 MS.
1,000,000,000 Iterations -> 4065.19 MS.

COS / SIN方法

1,000 Iterations -> 0.581016 MS or 581016 NanoSeconds.
100,000 Iterations -> 25.0049 MS.
100,000,000 Iterations -> 24,731.6 MS.
1,000,000,000 Iterations -> 246,096 MS.

因此,总结一下上面执行的cos(###)和sin(###),我的策略允许每秒大约220,000,000次执行。利用最初显示的计算机规格。这是相当快的,并且利用非常少的内存,因此它是通常在C ++中找到的数学cos / sin函数的绝佳替代品。如果你想看到精确度打开上面显示的链接,并且有一个0度到359的打印。此外,这支持0到89和象限0到3.所以你需要使用它或执行( DEGREES%90)。

答案 2 :(得分:3)

如果您想使用自定义实施,请查看hereherehere

如果您需要计算大型数组的sin / cos,还可以here(滚动到通用SIMD-Mathlibrary)

您也可以尝试使用C ++ SSE内在函数。看here

请注意,大多数现代编译器都支持SSE和SSE2优化。例如,对于Visual Studio 2010,您需要手动启用它。完成此操作后,大多数标准数学函数将使用不同的实现。

还有一个选择是使用DirectX HLSL。看here。请注意,有一个很好的sincos函数可以返回sin和cos。

通常,我使用IPP(不是免费的)。有关详细信息,请查看here

答案 3 :(得分:3)

Quake 3的源码有一些针对速度超过精度的预先计算的正弦/余弦的代码,它不是基于因此非常便携的(在架构和内在api上)。您可能还会发现基于sse和sse2的函数的摘要非常有趣:http://gruntthepeon.free.fr/ssemath/

答案 4 :(得分:2)

A)试图保存小百分比并不会令人满意。在97而不是100小时完成仍然很长时间。

B)你说你描述了,并且trig函数比你想要的花费更多的时间。 多少?剩下的时间怎么样? 很可能你有更大的鱼来煎炸。 大多数分析器based on the gprof concepts都没有告诉您有关中间堆栈调用的信息,您可以专注于节省大量时间。 Here's an example.

答案 5 :(得分:2)

我在cpu端实现了一个快速正弦函数,它比math.h的正弦函数至少快两倍,但是我使用了一个非常小的查找表(20个浮点数)。它的准确性也一点都不差;平均相对错误率为0.095%。你可以从http://www.hevi.info/tag/fast-sine-function/

查看

该方法的解释非常简单,并依赖于小a的罪(a)= a * pi / 180(见上面的证据链接)

enter image description here

一些三角函数

虽然可以使用上面显示的公式对0到10之间的角度获得相对准确的结果,但随着角度变得更宽,角度变得更宽。因此我们应该使用小于10的角度的公式但是如何?!

答案来自三角正弦加法公式;

sin(a + b)= sin(a)cos(b)+ sin(b)cos(a)

如果我们可以保持'b'小于10,那么我们将能够使用我们的公式,以便通过几个结构操作找到正弦。

假设我们被问到71.654的正弦值,那么;

a = 70

b = 1.654

罪(71.654)=罪(70 + 1.654)=罪(70)cos(1.654)+罪(1.654)cos(70)

在这个公式中,我们能够使用sin(1.654)部分的快速计算,不幸的是我们需要有正弦和余弦表。好消息是我们只需要乘以10的正弦和自然数角度在0到10之间的余弦。

答案 6 :(得分:1)

很久以前,在慢速机器上,人们使用具有预先计算值的数组。另一个用你自己的精度计算的选项,如this :(寻找“系列定义”)

答案 7 :(得分:1)

您可以查看this。它谈到优化罪,cos。

答案 8 :(得分:0)

对于2-3%的增益,这几乎肯定不值得出现不准确,错误,假设不再存在的风险(例如永远不会超出[-1,-1])等等,除非您计划运行这在大量机器上(其中2-3%代表数千或数百万美元的电费和机器的摊销成本)。

也就是说,如果您有关于要完成的内容的特定领域知识,那么您可以将计算速度提高两倍或更多。例如,如果您始终需要具有相同值的sincos,请在代码中将它们彼此接近地计算,并确保您的编译器将它们转换为FSINCOS汇编指令(请参阅{{3 }})。如果只需要函数全部范围的一小部分,则可以使用一组低阶多项式,然后迭代牛顿方法以获得完整的机器精度(或根据需要)。同样,如果你知道你只需要一些价值,那就更强大了 - 例如。如果你可以使用sin(x)接近零接近x,并且你只需要接近零的值,那么你可以大大减少你需要的术语数量。

但是,我的主要建议是:2-3%不值得。在优化之前,要更加思考所使用的​​算法和其他潜在的瓶颈(例如,malloc是否需要花费太多时间?)。