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

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

JLS 8.4.3.5,strictfp方法:

strictfp修饰符的作用是使方法体内的所有float或double表达式都是显式FP-strict(第15.4节)。

JLS 15.4 FP-strict表达式:

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

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

我一直试图找到一种方法来获得strictfp方法中的表达式和非strictfp的表达式之间的实际差异。 我在两台笔记本电脑上试过这个,一台配备英特尔酷睿i3 CPU,另一台配备英特尔酷睿i7 CPU。 我不能有任何区别。

很多post都建议原生浮点(不使用strictfp )可以使用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); } 

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

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

  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位,就像我预期的那样。 查看 movsdmulsddivsd 的 文档 ,他们都提到这些(SSE)指令操作64位浮点值,而不是我预期的80位值。 因此,这些指令操作的双值集已经是IEEE 754值集合似乎是合乎逻辑的,因此在使用strictfp和没有它之间没有区别。

我的问题是:

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

如果用“modern”表示处理器支持你在编译器中产生的SSE2指令( mulsd ,…),那么答案是否定的, strictfp没有区别,因为指令集没有允许利用缺乏strictfp优势。 可用指令已经最佳,可以根据strictfp的精确规格进行计算。 换句话说,在那种现代CPU上,您可以始终以相同的价格获得strictfp语义。

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

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

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

Interesting Posts