12

什么时候设置了针对没有 SSE2 的英特尔处理器的 Java 运行时如何处理浮点非规范化strictfp

即使将 387 FPU 设置为 53 位精度,它也会保持一个过大的指数范围:

  1. 强制检测每个中间结果的下溢/溢出,以及
  2. 使得很难避免非正规数的双舍入。

策略包括使用模拟浮点重新计算导致非规范值的操作,或沿着该技术的线的永久指数偏移以使 OCaml 配备 63 位浮点数,从指数中借用一点以避免双重-四舍五入。

无论如何,我认为没有办法避免每个浮点计算至少有一个条件分支,除非可以静态确定该操作不会下溢/溢出。如何处理异常(上溢/下溢)情况是我的问题的一部分,但这不能与表示问题分开(例如,永久指数偏移策略似乎意味着只需要检查溢出)。

4

1 回答 1

10

在我看来,从一个非常微不足道的测试用例来看,就像 JVM 通过内存往返每个double计算以获得它想要的舍入。它似乎也用几个魔法常数做了一些奇怪的事情。这是一个简单的“天真地计算 2^n”程序为我所做的:

0xb1e444b0: fld1
0xb1e444b2: jmp    0xb1e444dd         ;*iload
                                      ; - fptest::calc@9 (line 6)
0xb1e444b7: nop
0xb1e444b8: fldt   0xb523a2c8         ;   {external_word}
0xb1e444be: fmulp  %st,%st(1)
0xb1e444c0: fmull  0xb1e44490         ;   {section_word}
0xb1e444c6: fldt   0xb523a2bc         ;   {external_word}
0xb1e444cc: fmulp  %st,%st(1)
0xb1e444ce: fstpl  0x10(%esp)
0xb1e444d2: inc    %esi               ; OopMap{off=51}
                                      ;*goto
                                      ; - fptest::calc@22 (line 6)
0xb1e444d3: test   %eax,0xb3f8d100    ;   {poll}
0xb1e444d9: fldl   0x10(%esp)         ;*goto
                                      ; - fptest::calc@22 (line 6)
0xb1e444dd: cmp    %ecx,%esi
0xb1e444df: jl     0xb1e444b8         ;*if_icmpge
                                      ; - fptest::calc@12 (line 6)

我相信0xb523a2c80xb523a2bc_fpu_subnormal_bias1来自_fpu_subnormal_bias2热点源代码。 _fpu_subnormal_bias1看起来是0x03ff8000000000000000_fpu_subnormal_bias2看起来是0x7bff8000000000000000_fpu_subnormal_bias1具有将最小法线缩放到最小法线的double效果long double;如果 FPU 舍入到 53 位,“正确的事情”就会发生。

我推测存在看似毫无意义的test指令,以便在需要 GC 的情况下通过将该页面标记为不可读来中断线程。

这是Java代码:

import java.io.*;
public strictfp class fptest {
 public static double calc(int k) {
  double a = 2.0;
  double b = 1.0;
  for (int i = 0; i < k; i++) {
   b *= a;
  }
  return b;
 }
 public static double intest() {
  double d = 0;
  for (int i = 0; i < 4100; i++) d += calc(i);
  return d;
 }
 public static void main(String[] args) throws Exception {
  for (int i = 0; i < 100; i++)
   System.out.println(intest());
 }
}

进一步挖掘,这些操作的代码在hotspot/src/cpu/x86/vm/x86_63.ad. 相关片段:

instruct strictfp_mulD_reg(regDPR1 dst, regnotDPR1 src) %{
  predicate( UseSSE<=1 && Compile::current()->has_method() && Compile::current()
->method()->is_strict() );
  match(Set dst (MulD dst src));
  ins_cost(1);   // Select this instruction for all strict FP double multiplies

  format %{ "FLD    StubRoutines::_fpu_subnormal_bias1\n\t"
            "DMULp  $dst,ST\n\t"
            "FLD    $src\n\t"
            "DMULp  $dst,ST\n\t"
            "FLD    StubRoutines::_fpu_subnormal_bias2\n\t"
            "DMULp  $dst,ST\n\t" %}
  opcode(0xDE, 0x1); /* DE C8+i or DE /1*/
  ins_encode( strictfp_bias1(dst),
              Push_Reg_D(src),
              OpcP, RegOpc(dst),
              strictfp_bias2(dst) );
  ins_pipe( fpu_reg_reg );
%}

instruct strictfp_divD_reg(regDPR1 dst, regnotDPR1 src) %{
  predicate (UseSSE<=1);
  match(Set dst (DivD dst src));
  predicate( UseSSE<=1 && Compile::current()->has_method() && Compile::current()
->method()->is_strict() );
  ins_cost(01);

  format %{ "FLD    StubRoutines::_fpu_subnormal_bias1\n\t"
            "DMULp  $dst,ST\n\t"
            "FLD    $src\n\t"
            "FDIVp  $dst,ST\n\t"
            "FLD    StubRoutines::_fpu_subnormal_bias2\n\t"
            "DMULp  $dst,ST\n\t" %}
  opcode(0xDE, 0x7); /* DE F8+i or DE /7*/
  ins_encode( strictfp_bias1(dst),
              Push_Reg_D(src),
              OpcP, RegOpc(dst),
              strictfp_bias2(dst) );
  ins_pipe( fpu_reg_reg );
%}

我看不到任何加法和减法,但我敢打赌,他们只是在 53 位模式下使用 FPU 进行加法/减法,然后通过内存将结果往返。我有点好奇是否有一个棘手的溢出案例会导致他们出错,但我没有足够的好奇心去找出答案。

于 2013-08-28T21:23:42.757 回答