Java strictfp 修饰符对现代 CPU 有什么影响吗?

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

JLS 8.4.3.5,严格fps方法:

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

JLS 15.4 FP 严格表达式:

在 FP 严格表达式中,所有中间值都必须是浮点值集或双精度值集的元素,这意味着所有 FP 严格表达式的结果必须是 IEEE 754 算术对使用单一和双精度格式表示的操作数预测的结果。

在不严格 FP 的表达式中,为实现提供了一些余地,以使用扩展的指数范围来表示中间结果;粗略地说,净效应是,在排除使用浮点值集或双精度值集可能导致溢出或下溢的情况下,计算可能会产生“正确答案”。

我一直在尝试想出一种方法来获得方法中的表达式与非.我已经在两台笔记本电脑上尝试过,一台使用Intel Core i3 CPU,另一台使用Intel Core i7 CPU。我得不到任何区别。strictfpstrictfp

许多帖子表明,不使用 的原生浮点数可能使用 80 位浮点数,并且在最小可能的 java 双精度值(最接近零)或高于可能的最高 64 位 java 双精度值时,具有额外的可表示数字。strictfp

我在下面尝试了带和不带修饰符的代码,它给出了完全相同的结果。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);
}

实际上,我假设任何差异只会在代码编译为程序集时才会出现,因此我使用JVM参数运行它。但没有区别。-Xcomp

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

  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

答案 1

如果“现代”是指支持您在问题中引用的由编译器(,...)生成的SSE2指令的处理器,那么答案是否定的,没有区别,因为指令集不允许利用缺少.可用的指令已经是最佳,可以按照 的精确规格进行计算。换句话说,在这种现代CPU上,你总是以相同的价格获得语义。mulsdstrictfpstrictfpstrictfpstrictfp

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

为387编译的典型计算将看起来像这个答案中的汇编,通过精心选择的2次幂进行放置良好的乘法,以使下溢的行为与IEEE 754 binary64中的行为相同。然后,通过 64 位内存位置对结果进行往返,以处理溢出。strictfp

在没有基本操作的情况下编译的相同计算将为每个基本操作生成一条387指令,例如,仅源级乘法的乘法指令。(在程序开始时,387 将被配置为使用与 binary64 相同的有效宽度,即 53 位。strictfpfmulp


答案 2

推荐