Java 使用;sincos“;在爪哇

Java 使用;sincos“;在爪哇,java,math,optimization,trigonometry,Java,Math,Optimization,Trigonometry,在很多情况下,我不仅需要正弦,还需要同一参数的余弦 对于C,在通用unixm数学库中有sincos函数。实际上,至少在i386上,这应该是一条汇编指令,fsincos sincos,sincosf,sincosl-同时计算sin和cos 我想这些好处的存在是因为在计算正弦和余弦时有明显的重叠:sin(x)^2+cos(x)^2=1。但是,如果尝试将此快捷方式设置为cos=Math.sqrt(1-sin*sin),则不会有任何回报,因为sqrt函数的成本类似 有没有办法在Java中获得同样的好处?

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

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

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

我想这些好处的存在是因为在计算正弦和余弦时有明显的重叠:
sin(x)^2+cos(x)^2=1
。但是,如果尝试将此快捷方式设置为
cos=Math.sqrt(1-sin*sin)
,则不会有任何回报,因为
sqrt
函数的成本类似

有没有办法在Java中获得同样的好处?我想我会为双倍[]付出代价;由于增加了垃圾收集,这可能使所有的努力都没有意义

或者Hotspot编译器是否足够聪明,能够识别出我需要这两种编译器,并将其编译为
sincos
命令?我是否可以测试它是否识别它,以及我是否可以帮助它识别它,例如,通过确保
Math.sin
Math.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
应该需要一个额外的pop,但应该在1个时钟周期内完成。假设CPU也没有对此进行优化,
sincos
的速度应该是调用
sin
两次的两倍(第二次计算余弦;因此我认为它需要进行加法)
sqrt
在某些情况下可能更快,但sine可能更快


更新:我在C语言中做了一些实验,但没有结论。有趣的是,
sincos
似乎比
sin
(没有
cos
)还要快一点,当您同时计算
sin
cos
时,GCC编译器将使用
fsincos
,所以它做了我希望Hotspot做的事情(或者Hotspot也做了?)。我还不能通过使用
fsincos
来阻止编译器胜过我,除非不使用
cos
。然后它将返回到C
sin
,而不是
fsin

大多数sin和cos计算直接调用硬件。没有比这更快的计算方法了。具体来说,在+-pi/4范围内,速率非常快。如果您一般使用硬件加速,并尝试将这些值限制为指定的值,则应该可以

您始终可以配置文件

但是,一般来说,sqrt的速度应该与division相同,因为div和sqrt的内部实现非常相似

Sin和cosine,OTOH是用高达10度的多项式计算的,没有任何公共系数,可能还有一个困难的模2pi约化——这是sincos中唯一共享的公共部分(不使用CORDIC时)

编辑修改后的配置文件(已更正打字错误)显示

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

查看热点代码,我相当确信Oracle热点VM不会将sin(a)+cos(a)优化为fsincos:请参阅,第7482ff行

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


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

我已经用caliper执行了一些微基准测试。在-4*pi.范围内的(预计算)随机数数组上进行10000000次迭代。。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
    +sinvia
    Math.sqrt((1+cos)*(1-cos))
    +符号重建
(1+sin)*(1-sin)=1-sin*sin
数学上,但如果sin接近1,它应该更精确?运行时差异最小,您只需保存一个添加项

通过
x%=TWOPI进行符号重建;如果(x)
测试。我认为我的结果不支持
sin+sqrt
cos+sqrt
显然更可取,但它们确实比
sin
然后
cos
节省了大约40%的时间

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

如果我再运行一次,我还将添加JNI
sin
noop
来估计JNI的成本。也许还可以通过JNI对
sqrt
技巧进行基准测试。只是为了确保我们确实需要一个内在的
sincos

没有Sin 1,30 ============== Cos 1,29 ============== Sin, Cos 2,52 ============================ JNI sincos 1,77 =================== SinSqrt 1,49 ================ CosSqrt 1,51 ================
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
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