正弦和余弦哪个更有效? Sin 和 Cos 还是 Sin 和 Sqrt?

问题描述 投票:0回答:4

不幸的是,标准 C++ 库没有对 sincos 进行一次调用,这为这个问题提供了空间。

第一个问题:

如果我想计算 sin 和 cos,是先计算 sin 和 cos 还是先计算 sin 再用 sqrt(1-sin^2) 来得到 cos 比较便宜?

第二个问题:

英特尔数学内核库为标准数学函数计算提供了非常好的函数,因此函数 vdSinCos() 的存在可以以非常优化的方式解决问题,但英特尔编译器不是免费的。 Linux 发行版中是否有可用的开源库(C、C++、Fortran)具有这些功能,我可以简单地链接到它们并获得最佳实现?

注意:我不想讨论指令调用,因为并非所有 CPU 都支持它们。我想链接到一个通用库,它可以在任何 CPU 上为我完成这项工作。

谢谢。

c++ performance optimization sqrt trigonometry
4个回答
9
投票

GNU C 库具有 sincos() 函数,它将利用大多数现代指令集所具有的“FSINCOS”指令。我想说这是你最好的选择;它应该和英特尔库方法一样快。

如果你不这样做,我会选择“sqrt(1-sin(x)^2)”路线。到目前为止,在我看过的每个处理器架构文档中,FSQRT 指令都比 FSIN 函数快得多。


1
投票

几乎每个性能问题的答案都是“为什么不在代码中对其进行测量”,因为有大量不同的因素会影响几乎任何此类计算的性能。例如,“谁生成数学函数”。平方根计算起来相对简单,但我不相信

sqrt(1-sin*sin)
和再次计算
cos
之间存在巨大差异。什么处理器也可能是一个因素,以及“围绕”正弦/余弦计算进行哪些其他计算。

如果某个地方有一个具有此类功能的图书馆,我不会感到惊讶,但我还没有寻找过。


0
投票

如果精度不重要,获得 sin 或 cos 的最快方法是使用表格。 保存一些全局常量数组,其中包含所有角度的 sin 和 cos 值以及您需要的步骤。因此,您的 sin/cos 函数只需将角度转换为索引即可获得结果。


0
投票

我遇到了同样的问题,所以我对其进行了基准测试。结果是,根据您所做的事情,您的编译器可能比您更擅长优化,即使它不使用内在的

sincos
函数。

我编写了一个小测试程序来测试使用

sincos
内在函数、
std::sin
std::cos
以及
std::sin
和从
sqrt(1-sin*sin)

计算出的 cos

测试涉及从 0-

2*M_PI
生成 1e8 个随机数。每个测试都计算每个随机数的 sin 和 cos,对值进行求和,然后将总和输出到标准输出 - 这确保了整个程序不会被优化。我用 O3 和 fp:fast 编译

使用

sqrt(1-sin*sin)

 是迄今为止最慢的。这是因为我需要一个 if 语句来检查结果的符号。这意味着循环无法矢量化。

其他选项的速度相似。最初,我创建了一个

fastSinCos

 函数,它接受 4 个双精度数并返回 4 个双精度数。然后我将 4 个双打加到总和中。这比仅使用 
sum += std::sin(input[i])+std::cos(input[i])
 慢。事实证明,编译器在幼稚的实现中对总和进行了向量化,所以这样打败了我。

当我修改代码来创建一个“fastSinCosSum”函数,其中总和被矢量化时,我成功地击败了简单的版本,但仅提高了 10%。

如果我将输入范围限制为

M_PI/2.0

-
3.0*M_PI/2.0
,这样我就知道 cos 的结果始终为负,那么速度与初始版本相同。

由于 1e8 双倍比我的缓存大,我怀疑缓存未命中可能是实际的瓶颈。然而,即便如此,测试只花了大约一秒钟的时间来运行,所以担心它似乎很愚蠢。

所以最后,除非最理想化设置中 10% 的增益对您很重要,否则我怀疑您最好确保编译器可以向量化而不是尝试使用内部函数。三个选项生成的程序集如下所示。

fastSinCosSum(&inputs[i], r_sum3); //pass in the address of the first of the 4 elements to use and a register to store 4 sums 00007FF7FC321460 vmovupd ymm0,ymmword ptr [r14+rdi*8] 00007FF7FC321466 call __vdecl_sincos4 (07FF7FC322AD0h) 00007FF7FC32146B vaddpd ymm0,ymm0,ymmword ptr [r_sum3] 00007FF7FC321470 vaddpd ymm0,ymm0,ymm1 00007FF7FC321474 vmovupd ymmword ptr [r_sum3],ymm0 sum2 += std::sin(inputs[i]) + std::cos(inputs[i]); // just calculate naively 00007FF7FC321305 vmovupd ymm0,ymmword ptr [r14+rbx*8] 00007FF7FC32130B call __vdecl_cos4 (07FF7FC322A80h) 00007FF7FC321310 vmovupd ymmword ptr [rbp+60h],ymm0 00007FF7FC321315 vmovupd ymm0,ymmword ptr [r14+rbx*8] 00007FF7FC32131B call __vdecl_sin4 (07FF7FC322AA0h) 00007FF7FC321320 vaddpd ymm1,ymm0,ymmword ptr [rbp+60h] 00007FF7FC321325 vaddpd ymm0,ymm1,ymmword ptr [rbp+20h] 00007FF7FC32132A vmovupd ymmword ptr [rbp+20h],ymm0 double sin = std::sin(inputs[i]); 00007FF607CD1363 vmovupd ymm0,ymmword ptr [r14+rbx*8] 00007FF607CD1369 call __vdecl_sin4 (07FF607CD2A70h) sum2a += sin - std::sqrt(1 - sin * sin); // calculate cos using sqrt. The angles are limited so we know the sign of the result is negative 00007FF607CD136E vmovupd ymm1,ymmword ptr [__ymm@3ff00000000000003ff00000000000003ff00000000000003ff0000000000000 (07FF607CD7480h)] 00007FF607CD1376 vfnmadd231pd ymm1,ymm0,ymm0 00007FF607CD137B vsqrtpd ymm1,ymm1 00007FF607CD137F vsubpd ymm0,ymm0,ymm1 00007FF607CD1383 vaddpd ymm1,ymm0,ymmword ptr [rbp+60h] 00007FF607CD1388 vmovupd ymmword ptr [rbp+60h],ymm1
    
© www.soinside.com 2019 - 2024. All rights reserved.