24

我想知道,当我在Reddit 线程中发现问题时,为什么要Math.sin(double)委托。提到的代码片段如下所示(JDK 7u25):StrictMath.sin(double)

数学.java

public static double sin(double a) {
    return StrictMath.sin(a); // default impl. delegates to StrictMath
}

严格数学.java

public static native double sin(double a);

第二个声明native对我来说是合理的。的文档Math指出:

鼓励代码生成器在可用的情况下使用特定于平台的本机库或微处理器指令(...)

问题是:实现StrictMath平台特定的本机库还不够吗?JIT 对平台的了解比已安装的 JRE 还多(请只关注这种情况)?换句话说,为什么不是Math.sin()原生的呢?

4

4 回答 4

17

我将尝试在一篇文章中结束整个讨论。

通常,Math代表StrictMath. 显然,调用可以内联,所以这不是性能问题。

StrictMath是一个最终类,其native方法由本机库支持。有人可能会认为,native 意味着最佳,但不一定是这种情况。浏览StrictMathjavadoc 可以阅读以下内容:

(...) 这个包中一些数值函数的定义要求它们产生与某些已发布算法相同的结果。这些算法可从著名的网络库 netlib 中以包“Freely Distributable Math Library”fdlibm 的形式获得。这些算法是用 C 编程语言编写的,因此可以理解为按照 Java 浮点运算规则执行所有浮点运算。

我对这个文档的理解是,本机库实现StrictMath是根据fdlibm库实现的,它是多平台的并且已知会产生可预测的结果。因为它是多平台的,所以不能指望它在每个平台上都是最佳实现,我相信这是智能 JIT 可以微调实际性能的地方,例如通过输入范围的统计分析和调整算法/相应地实施。

深入研究实现很快就会发现,备份的本机库StrictMath实际上使用 fdlibm

OpenJDK 7 中的StrictMath.c源代码如下所示:

   #include "fdlibm.h"
   ...
   JNIEXPORT jdouble JNICALL
   Java_java_lang_StrictMath_sin(JNIEnv *env, jclass unused, jdouble d)
   {
       return (jdouble) jsin((double)d);
   }

并且正弦函数在fdlibm/src/s_sin.c中定义,在一些地方引用了__kernel_sin直接来自头文件fdlibm.h的函数。


虽然我暂时接受自己的答案,但当它出现时,我很乐意接受一个更有能力的答案。

于 2013-07-10T18:10:49.123 回答
4

为什么 Math.sin() 委托给 StrictMath.sin()?

JIT 编译器应该能够内联StrictMath.sin(a)调用。因此,native为案例创建一个额外的方法 Math.sin()并添加额外的 JIT 编译器智能来优化调用序列等没有什么意义。

鉴于此,您的反对实际上归结为“优雅”问题。但“务实”的观点更有说服力:

  • 更少的本机调用使 JVM 核心和 JIT 更易于维护、更不脆弱等。

  • 如果它没有损坏,请不要修复它。

至少,这就是我想象Java 团队会如何看待这一点的方式。

于 2013-07-01T18:07:29.243 回答
4

该问题假设 JVM 实际运行委托代码。在许多 JVM 上,它不会。对 Math.sin() 等的调用可能会被 JIT 透明地替换为一些内部函数代码(如果合适)。这通常会以最终用户无法观察到的方式完成。对于 JVM 实现者来说,这是一个常见的技巧,可能会发生有趣的特化(即使该方法没有标记为原生)。

但是请注意,由于合适的输入范围,大多数平台不能简单地将单处理器指令用于 sin(例如,请参阅:英特尔讨论)。

于 2013-07-09T14:52:35.103 回答
1

Math API 允许对其方法进行非严格但性能更好的实现,但不需要它,默认情况下,Math 仅使用 StrictMath impl。

于 2013-07-01T18:19:42.030 回答