15

strictfp根据 JLS,我知道修饰符对方法(和类)的含义:

JLS 8.4.3.5,strictfp 方法:

strictfp 修饰符的作用是使方法主体内的所有浮点或双精度表达式都显式地为 FP-strict(第 15.4 节)。

JLS 15.4 FP 严格表达式:

在 FP-strict 表达式中,所有中间值必须是 float 值集或 double 值集的元素,这意味着所有 FP-strict 表达式的结果必须是 IEEE 754 算术对使用单双格式表示的操作数预测的结果.

在非 FP 严格的表达式中,允许实现使用扩展指数范围来表示中间结果;粗略地说,最终效果是,在独占使用浮点值集或双精度值集可能导致上溢或下溢的情况下,计算可能会产生“正确答案”。

我一直在尝试想出一种方法来获得方法中的表达式与非strictfp方法中的表达式之间的实际差异strictfp。我在两台笔记本电脑上试过这个,一台配备 Intel Core i3 CPU,另一台配备 Intel Core i7 CPU。而且我看不出任何区别。

许多帖子表明,不使用的本机浮点数strictfp可能使用 80 位浮点数,并且在可能的最小 java double(最接近零)或最高可能的 64 位 java double 之上具有额外的可表示数字。

我在下面尝试了使用和不使用strictfp修饰符的代码,它给出了完全相同的结果。

public static strictfp void withStrictFp() {
    double v = Double.MAX_VALUE;
    System.out.println(v * 1.0000001 / 1.0000001);
    v = Double.MIN_VALUE;
    System.out.println(v / 2 * 2);
}

实际上,我认为只有在将代码编译为汇编时才会出现任何差异,因此我使用-XcompJVM 参数运行它。但没有区别。

我发现另一篇文章解释了如何获取 HotSpot 生成的汇编代码(OpenJDK 文档)。我正在运行我的代码java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly。第一个v * 1.0000001 / 1.0000001带有修饰符的表达式 ( ) 和没有修饰符的表达式strictfp相同,编译为:

  0x000000010f10a0a9: movsd  -0xb1(%rip),%xmm0        # 0x000000010f10a000
                                                ;   {section_word}
  0x000000010f10a0b1: mulsd  -0xb1(%rip),%xmm0        # 0x000000010f10a008
                                                ;   {section_word}
  0x000000010f10a0b9: divsd  -0xb1(%rip),%xmm0        # 0x000000010f10a010
                                                ;   {section_word}

该代码中没有任何内容可以像我预期的那样将每个步骤的结果截断为 64 位。查找 和 的 文档,他们都提到这些(SSE)指令在 64 位浮点值上运行,而不是我预期的 80 位值。因此,这些指令操作的双值集已经是 IEEE 754 值集似乎是合乎逻辑的,因此拥有和不拥有它没有区别。movsdmulsddivsdstrictfp

我的问题是:

  1. 这个分析正确吗?我不经常使用英特尔组件,所以我对我的结论没有信心。
  2. 是否有任何(其他)现代 CPU 架构(具有 JVM)在使用和不使用strictfp修饰符的操作之间存在差异?
4

1 回答 1

11

如果“现代”是指支持您在问题中引用的由编译器生成的那种 SSE2 指令的处理器(mulsd...),那么答案是否定的,strictfp没有区别,因为指令集不允许利用缺席的优势strictfp。可用的指令对于计算strictfp. 换句话说,在这种现代 CPU 上,您strictfp始终以相同的价格获得语义。

如果“现代”是指历史上的 387 FPU,那么如果中间计算在strictfp模式下溢出或下溢,则可以观察到差异(不同之处在于它可能不会溢出或在下溢时保持比预期更多的精度位)。

为 387 编译的典型strictfp计算将类似于此答案中的程序集,通过精心选择的 2 幂进行适当的乘法运算,以使下溢的行为与 IEEE 754 binary64 中的相同。然后通过 64 位内存位置的结果往返处理溢出。

没有编译的相同计算strictfp将在每个基本操作中产生一条 387 指令,例如仅fmulp用于源级乘法的乘法指令。(在程序开始时,387 将被配置为使用与 binary64、53 位相同的有效位宽度。)

于 2014-03-22T00:32:07.463 回答