如果设置strictfp
,那么针对没有SSE2的英特尔处理器的Java运行时如何处理浮点非正规数?
即使将387 FPU设置为53位精度,它仍保持超大的指数范围:
策略包括重新计算导致模拟浮点的非正规值的操作,或沿this technique行的永久指数偏移,以便为OCaml配备63位浮点数,从中借用一点指数,以避免双舍入。
在任何情况下,我都认为没有办法避免每个浮点计算至少有一个条件分支,除非静态地确定操作不会下溢/溢出。 如何处理异常(溢出/下溢)情况是我的问题的一部分,但这不能与表示的问题分开(永久指数偏移策略似乎意味着只需要溢出例如,检查。
答案 0 :(得分:9)
在我看来,从一个非常简单的测试用例来看,就像JVM通过内存进行每次double
计算往返一样,以获得它想要的舍入。它似乎也用一些魔法常数做了一些奇怪的事情。这就是它为我做一个简单的“计算2天真”程序:
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)
我相信来自热点源代码的0xb523a2c8
和0xb523a2bc
是_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
中的OpenJDK代码中非常明显。相关摘要:
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进行加/减,然后通过内存往返往返。我有点好奇是否有一个棘手的溢出案例,他们出错了,但我并不好奇地发现。