在Java中使用“正余弦”(Using “sincos” in Java)

2019-08-02 23:12发布

在很多情况我不仅需要正弦,也同样参数的余弦值。

对于C,有sincos在常见的UNIX功能m数学库。 而实际上,至少在i386上,这应该是一个单一的汇编指令, fsincos

正余弦,sincosf,sincosl - 同时计算正弦和余弦

我想存在这些好处,因为在计算正弦和余弦的明显重叠: sin(x)^2 + cos(x)^2 = 1 。 但据我所知它没有还清,试图走捷径这是cos = Math.sqrt(1 - sin*sin)因为sqrt函数是在一个类似的成本。

有什么办法谋取在Java中同样的好处? 我想我会为此付出代价的double[]即可; 这可能使因为添加的垃圾收集的实际意义所作的一切努力。

或者说是热点编译器足够聪明,意识到我需要两个,并将汇编这一个sincos命令? 我可以测试是否能够识别它,我能帮助它认识到这一点,例如通过确保Math.sinMath.cos命令是在我的代码直接连续? 这实际上使得从一个Java语言点最有意义:具有comiler优化这种使用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 ),以及GCC编译器将使用fsincos当你同时计算sincos -因此它希望做什么,我热点做(或做热点,太? )。 我还不能阻止编译器使用智胜我fsincos除了不使用cos 。 然后它会回落到一个C sin ,不fsin

Answer 1:

我曾与卡尺进行一些微基准。 千万次迭代过的随机数的范围为-4 * PI一个(预计算)阵列。4 * PI。 我尽我所能,以获得最快的JNI解决方案,我可以拿出去-这是一个有点很难预测是否会真正得到fsincos或一些模拟的sincos 。 报告的数字是最好的10个卡钳试验(其又包括3-10试验,其中的报道平均值)。 所以大概是30-100每一个内循环的运行。

我已经基准几个变种:

  • Math.sin只(参考)
  • Math.cos只(参考)
  • Math.sin + Math.cos
  • sincos通过JNI
  • Math.sin经由+ COS Math.sqrt( (1+sin) * (1-sin) ) +符号重建
  • Math.cos经由+罪Math.sqrt( (1+cos) * (1-cos) ) +符号重建

(1+sin)*(1-sin)=1-sin*sin数学,但如果单是接近于1应该是更精确的? 运行时不同的是最小的,你救一个加法。

注册通过重建x %= TWOPI; if (x<0) x+=TWOPI; 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基础的方法胜手了对任何其他人(如我们目前无法访问sincos纯Java)。 JNI的sincos比计算更好sincos ,但sqrt的做法仍然较快。 cos本身似乎是一致蜱(0,01)胜过sin ,但情况区别重建标志有一个额外>测试。 我不认为我的研究结果支持,要么sin+sqrtcos+sqrt显然preferrable,但他们并保存时相比40%左右sin ,然后cos

如果我们将扩展Java有一个内在的优化正余弦 ,那么这可能会更好。 恕我直言,这是一个常见的情况,例如在图形。 当在AWT中使用,蜡染等各种应用可以从中受益。

如果我再次运行此,我还要补充JNI sinnoop估计JNI的成本。 也许还标杆sqrt通过JNI伎俩。 只是为了确保我们其实也希望有一个内在的sincos ,从长远来看。



Answer 2:

大多数的正弦和余弦计算是直接在硬件的调用。 没有太多的更快的方法来计算的话比。 具体而言,在范围+ - π/ 4,该速率是非常快。 如果您使用硬件加速一般,并试图限制值的规定,你应该罚款。 源 。



Answer 3:

综观热点代码,我宁愿相信,甲骨文HotSpot虚拟机不优化罪(一)+ COS(一)到FSINCOS:见assembler_x86.cpp ,线7482ff。

然而,我会怀疑机器周期的使用单独FSIN和FCOS数量增加很容易被其它操作,如运行GC outshadowed。 我会用标准的Java功能和分析的应用程序。 只有当一个运行配置文件表明显著时间以度过罪/余弦电话,我敢说出来做一些事情。

在这种情况下,我会创建使用2元件jdoublearray作为输出参数JNI封装。 如果你有一个使用JNI正余弦操作只有一个线程,可以在将被重用一遍一遍Java代码中使用静态初始化双[2]数组。



Answer 4:

您可以随时轮廓。

然而,通常开方应该来以相同的速度为师,为内部实现DIV和开方的非常相似。

正弦和余弦函数,与高达10度的多项式计算OTOH没有任何共同的系数和可能的困难模2π减小 - 这是唯一的公共部分中的正余弦共享(未使用CORDIC时)。

EDIT修订分析(与错字校正的)示出了定时差为

sin+cos:  1.580 1.580 1.840 (time for 200M iterations, 3 successive trials)
sincos:   1.080 0.900 0.920
sin+sqrt: 0.870 1.010 0.860


Answer 5:

有没有在普通的Java提供FSINCOS。 另外,JNI版本可能比java.lang.Math.sin()和cos()双呼叫慢。

我猜你是关心的sin(x)的速度/ COS(X)。 所以,我给你快速三角操作的建议,在更换到FSINCOS:查找表。 下面是我原来的职位。 我希望它可以帮助你。

=====

我试图实现对三角函数(正弦和余弦)的最佳性能,使用查找表(LUT)。

我发现:

  • LUT可以更快20-25次 ,然后java.lang.Math.sin()/余弦()。 尽可能快的原生FSIN / FCOS。 也许快FSINCOS。
  • 但java.lang.Math.sin()和cos()比任何其他方式来计算正弦/余弦更快 ,如果你用0和45度之间的角度;
  • 但是,这角度低于12度通知具有的sin(x)==几乎X。 它甚至更快;

  • 一些实现使用float数组存储罪,另一个用于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.Note即暖机后,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


文章来源: Using “sincos” in Java