在Java中使用“sincos”

时间:2012-11-19 19:25:53

标签: java math optimization trigonometry

在很多情况下,我不仅需要正弦,还需要相同参数的余弦。

对于C,公共unix sincos数学库中有m函数。实际上,至少在i386上,这应该是一个汇编指令fsincos

  

sincos,sincosf,sincosl - 同时计算sin和cos

我猜这些好处是存在的,因为计算正弦和余弦有明显的重叠:sin(x)^2 + cos(x)^2 = 1。但是,AFAIK尝试将此快捷方式设为cos = Math.sqrt(1 - sin*sin)并不会付出代价,因为sqrt功能的代价相似。

有没有办法在Java中获得相同的好处?我想我要为double[]付出代价;由于添加了垃圾收集,这可能使所有的努力都没有实际意义。

或者Hotspot编译器是否足够智能以识别我需要两者,并将其编译为sincos命令?我可以测试它是否识别它,我可以帮助它识别它,例如通过确保Math.sinMath.cos命令在我的代码中直接连续?从Java语言的角度来看,这实际上是最有意义的:让编译器优化它以使用fsincos程序集调用。

从一些汇编文档中收集:

Variations    8087         287        387      486     Pentium
fsin           -            -       122-771  257-354   16-126  NP
fsincos        -            -       194-809  292-365   17-137  NP
 Additional cycles required if operand > pi/4 (~3.141/4 = ~.785)
sqrt        180-186      180-186    122-129   83-87    70      NP

fsincos应该需要额外的弹出,但这应该是1个时钟周期。假设CPU也没有对此进行优化,sincos应该几乎是调用sin两倍的速度(第二次计算余弦;所以我认为它需要进行添加)。在某些情况下sqrt可能更快,但正弦可以更快。

更新:我在C中做过一些实验,但它们尚无定论。有趣的是,sincos似乎比sin(没有cos)稍微快一点,并且当您同时计算fsincos时,GCC编译器将使用sin cos - 所以我做了Hotspot要做的事情(或者Hotspot也做了吗?)。除了不使用fsincos之外,我还不能阻止编译器使用cos来模糊我。然后它会回退到C sin,而不是fsin

3 个答案:

答案 0 :(得分:11)

我用卡尺执行了一些微基准测试。在-4 * pi ... 4 * pi范围内的(预先计算的)随机数阵列上进行10000000次迭代。我尽力获得最快的JNI解决方案 - 我有点难以预测你是实际获得fsincos还是模拟sincos。报告的数字是10个卡尺试验中最好的(其中包括3-10个试验,其中报告的平均值)。所以粗略地说,每个内循环运行30-100次。

我对几种变体进行了基准测试:

    仅限
  • Math.sin(参考)
  • 仅限
  • Math.cos(参考)
  • Math.sin + Math.cos
  • sincos来自JNI
  • Math.sin + cos via Math.sqrt( (1+sin) * (1-sin) ) + sign reconstruction
  • Math.cos +犯罪通过Math.sqrt( (1+cos) * (1-cos) ) +符号重建

(1+sin)*(1-sin)=1-sin*sin在数学上,但如果sin接近于1,它应该更精确吗?运行时间差异很小,您可以保存一个添加项。

通过x %= TWOPI; if (x<0) x+=TWOPI;签名重建,然后检查象限。如果你知道如何用更少的CPU来做到这一点,我会很高兴听到。

通过sqrt的数值损失似乎没问题,至少对于常见角度而言。在粗糙实验的1e-10范围内。

Sin         1,30 ==============
Cos         1,29 ==============
Sin, Cos    2,52 ============================
JNI sincos  1,77 ===================
SinSqrt     1,49 ================
CosSqrt     1,51 ================

sqrt(1-s*s)sqrt((1+s)*(1-s))的差异大约为0.01。正如您所看到的,基于sqrt的方法可以胜过任何其他方法(因为我们目前无法在纯Java中访问sincos)。 JNI sincos优于计算sincos,但sqrt方法仍然更快。 cos本身似乎始终是一个优于sin的刻度(0,01),但重建该符号的情况区别还有一个额外的>测试。我认为我的结果不支持sin+sqrtcos+sqrt显然是可取的,但与sin然后cos相比,它们确实节省了大约40%的时间。< / p>

如果我们将Java扩展为具有内在优化的sincos ,那么这可能会更好。恕我直言,这是一个常见的用例,例如在图形中。当在AWT,Batik等中使用时,许多应用程序都可以从中受益。

如果我再次运行此功能,我还会添加JNI sinnoop来估算JNI的费用。也许还可以通过JNI对sqrt技巧进行基准测试。只是为了确保我们确实从长远来看确实需要内在的sincos

答案 1 :(得分:1)

查看Hotspot代码,我相信Oracle Hotspot VM不会将sin(a)+ cos(a)优化为fsincos:请参阅assembler_x86.cpp,第7482ff行。

但是,我怀疑单独使用fsin和fcos的机器周期数增加很容易被运行GC等其他操作所掩盖。我会使用标准的Java功能并分析应用程序。只有当一个配置文件运行表明在sin / cos调用中花费了大量时间时,我才会冒险去做一些事情。

在这种情况下,我将创建一个使用2元素jdoublearray作为out参数的JNI包装器。如果您只有一个使用sincos JNI操作的线程,则可以在Java代码中使用静态初始化的double [2]数组,该数组将被反复使用。

答案 2 :(得分:1)

常规Java中没有 fsincos 。此外,JNI版本可能比对java.lang.Math.sin()和cos()的双重调用慢。

我猜你关心sin(x)/ cos(x)的速度。所以我给你一个快速三角运算的建议,取代fsincos:Look Up Table。以下是我的原帖。我希望它可以帮助你。

=====

我尝试使用查找表(LUT)在三角函数(sin和cos)上实现最佳性能。

我发现了什么:

  • LUT可以比java.lang.Math.sin()/ cos()快 20-25次。可能与原生fsin / fcos一样快。也许和fsincos一样快。
  • 但是,如果使用0到45度之间的角度,java.lang.Math.sin()和cos()比任何其他计算sin / cos的方式更快;
  • 但请注意,低于12度的角度sin(x)几乎== x。它甚至更快;

  • 有些实现使用float数组来存储sin,而另一个实现使用cos。这是不必要的。请记住:

cos(x) == sin(x + PI/2)

  • 也就是说,如果你有sin(x)表,你可以免费获得cos(x)表。

我用sin()对范围[0..45] 中的角度进行了一些测试,使用java.lang.Math.sin();一个用于360个位置的简单查找表,一个优化的LUT90,其表值为[0..90],但扩展为[0..360];并使用interpolation查找表。注意警告后,java.lang.Math.sin()比其他更快:

Size test: 10000000
Angles range: [0.0...45.0]
Time in ms
Trial | Math.sin() | Lut sin() | LUT90.sin() | Lut sin2() [interpolation]
0    312,5879        25,2280        27,7313      36,4127
1    12,9468         19,5467        21,9396      34,2344
2    7,6811          16,7897        18,9646      32,5473
3    7,7565          16,7022        19,2343      32,8700
4    7,6634          16,9498        19,6307      32,8087

此处提供的来源GitHub

但是,如果你需要范围[-360..360] 的高性能,java.lang.Math lib会更慢。查找表(LUT)快20倍左右。如果需要高精度,可以使用LUT进行插值,它比java.lang.Math慢一点但速度更快。在上面的链接上看到我在Math2.java中的sin2()。

以下数字适用于角度高范围:

Size test: 10000000
Angles range: [-360.0...360.0]
Time in ms
Trial|Math.sin() | Lut sin() | LUT90.sin() | Lut.sin2() [interpolation]
0    942,7756        35,1488        47,4198      42,9466
1    915,3628        28,9924        37,9051      41,5299
2    430,3372        24,8788        34,9149      39,3297
3    428,3750        24,8316        34,5718      39,5187