在很多情况下,我不仅需要正弦,还需要相同参数的余弦。
对于C,在通用的unix m
数学库中有sincos
函数。实际上,至少在 i386 上,这应该是一条汇编指令,fsincos
。
sincos, sincosf, sincosl - calculate sin and cos simultaneously
我猜这些好处的存在是因为在计算正弦和余弦时存在明显的重叠:sin(x)^2 + cos(x)^2 = 1
。但是 AFAIK 尝试将其简化为 cos = Math.sqrt(1 - sin*sin)
并没有返回,因为 sqrt
函数的成本相似。
有没有什么方法可以在 Java 中获得同样的好处?我想我要为 double[]
付出代价;由于添加了垃圾收集,这可能会使所有的努力都变得毫无意义。
或者 Hotspot 编译器是否足够聪明,可以识别我需要两者,并将其编译为 sincos
命令?我可以测试它是否识别它,我可以帮助它识别这一点,例如通过确保 Math.sin
和 Math.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
应该需要一个额外的 pop,但这应该在 1 个时钟周期内出现。假设 CPU 也没有对此进行优化,sincos
应该几乎是调用 sin
两次的两倍(第二次计算余弦;所以我认为它需要做增加项)。 sqrt
在某些情况下可能更快,但 sine 可能更快。
更新:我用 C 做了一些实验,但没有定论。有趣的是,sincos
似乎甚至比 sin
(没有 cos
)快一点,GCC 编译器将使用 fsincos
当你同时计算 sin
和 cos
- 所以它做了我希望 Hotspot 做的事情(或者 Hotspot 也做?)。除了不使用 cos
之外,我还无法阻止编译器通过使用 fsincos
来超越我。然后它将回落到 C sin
,而不是 fsin
。
最佳答案
我用卡尺执行了一些微基准测试。在 -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 通过Math.sqrt( (1+sin) * (1-sin) )
+ 符号重建 -
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
比计算更好 sin
和 cos
,但是 sqrt
方法还是比较快的。 cos
本身似乎始终比 sin
好一点 (0,01) , 但重建符号的大小写区别有一个额外的 >
测试。我认为我的结果也不支持 sin+sqrt
或 cos+sqrt
显然更可取,但与 sin
相比,它们确实节省了大约 40% 的时间然后 cos
.
如果我们将 Java 扩展为具有内在优化的 sincos,那么这可能会更好。恕我直言,这是一个常见的用例,例如在图形中。当用于 AWT、Batik 等时,许多应用程序都可以从中受益。
如果我再次运行它,我还会添加 JNI sin
和一个 noop
估算 JNI 的成本。也许还可以对 sqrt
进行基准测试通过 JNI 欺骗。只是为了确保我们确实想要一个内在的 sincos
从长远来看。
关于java - 在 Java 中使用 "sincos",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13460693/