java - 在 Java 中使用 "sincos"

标签 java math optimization trigonometry

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

对于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.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 应该需要一个额外的 pop,但这应该在 1 个时钟周期内出现。假设 CPU 也没有对此进行优化,sincos 应该几乎是调用 sin 两次的两倍(第二次计算余弦;所以我认为它需要做增加项)。 sqrt 在某些情况下可能更快,但 sine 可能更快。

更新:我用 C 做了一些实验,但没有定论。有趣的是,sincos 似乎甚至比 sin(没有 cos)快一点,GCC 编译器将使用 fsincos 当你同时计算 sincos - 所以它做了我希望 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比计算更好 sincos ,但是 sqrt方法还是比较快的。 cos本身似乎始终比 sin 好一点 (0,01) , 但重建符号的大小写区别有一个额外的 >测试。我认为我的结果也不支持 sin+sqrtcos+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/

相关文章:

haskell - 是否可以以不同的方式编写与 "textbook"一样快的阶乘函数?

java - 找不到指定的程序 (MapVirtualKeyEx) JNA (Java)

c++ - 围绕另一个点旋转一个点

java - 如何在进化算法中初始化染色体以求解实变量上的 LP/ILP 或一般 COS?

c++ - 如何计算指数/幂值(多少次幂),即 C++ 中的 'n'?

math - 给定两个点,如何通过它们画一条线?

javascript - AngularJS - 在哪里放置第三方插件默认值?

java - Mustache 中的字符串和字符串列表 - Java

java - 为什么我无法在 Spring MVC 中为 String 注册 PropertyEditor?

java - 带直线的 CodenamOne 条形图