针对SSE2之前处理器的Java运行时如何实现浮点基本操作?
当设置了针对SSE2之前处理器的Java运行时如何实现浮点基本操作?,java,floating-point,Java,Floating Point,当设置了strictfp时,针对没有SSE2的英特尔处理器的Java运行时如何处理浮点非规范化 即使将387 FPU设置为53位精度,它也会保持一个过大的指数范围,即: 强制在每个中间结果处检测下溢/溢出,以及 这样就很难避免非规范值的双舍入 策略包括重新计算导致使用模拟浮点值的非规范值的操作,或沿的永久指数偏移,以便为OCaml配备63位浮点,从指数中借用一位以避免双舍入 在任何情况下,我都无法避免每个浮点计算至少有一个条件分支,除非可以静态地确定该操作不会下溢/溢出如何处理异常(溢出/下溢)
strictfp
时,针对没有SSE2的英特尔处理器的Java运行时如何处理浮点非规范化
即使将387 FPU设置为53位精度,它也会保持一个过大的指数范围,即:
在任何情况下,我都无法避免每个浮点计算至少有一个条件分支,除非可以静态地确定该操作不会下溢/溢出如何处理异常(溢出/下溢)情况是我问题的一部分,但这不能与表示问题分开(例如,永久指数偏移策略似乎意味着只需要检查溢出)。在我看来,从一个非常琐碎的测试案例,与JVM一样,每次
double
计算都会通过内存进行往返,以获得所需的舍入。它似乎还做了一些奇怪的事情,有几个神奇的常数。下面是一个简单的“幼稚计算2^n”程序对我的帮助:
我相信0xb523a2c8
和0xb523a2bc
是热点源代码中的次正常偏差1
和次正常偏差2
<代码>fpu低于正常值偏差1看起来是0x03FF80000000000000
,fpu低于正常值偏差2
看起来是0x7FF80000000000000
<代码>\u fpu\u次正常值偏差1具有将最小正常值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());
}
}
我看不到加法和减法,但我敢打赌他们只是在53位模式下用FPU进行加法/减法运算,然后通过内存对结果进行往返运算。我有点好奇他们是否会出错,但我不太好奇。@ChrisJester-Young谢谢你帮我把问题弄清楚。我不知道你问题的答案。但是,如果您有这样一台机器,您可以传递标志-XX:+UnlockDiagnosticVMOptions-XX:+PrintAssembly
,查看它生成的代码。@tmyklebu我甚至不知道是否安装了Java运行时。我只对387的双精度模拟感兴趣。还有人曾经给我一本关于这个主题的研究生回忆录,我忘了存档,现在再也找不到了。不是你,是吗?看到代码,还没有完全理解它,这表明了“指数偏移”方法的一种变体。代码必须是这样做的:1-将一个参数乘以2^-K1,这样扩展指数乘法的结果是一个非规范的,其有效位数与标准指数2的结果相同-做乘积3-将结果乘以2^K2,这样结果要么是精确的,或+inf(正好是乘积溢出时的标准指数)4-乘以2^(K1-K2)。现在,第三个常量在哪里?等等,在你的方法calc
中,乘法的一个操作数显然是常量。这一个可以预先偏置,只剩下两个乘法来确保正确的下溢和溢出结果。我认为正在发生的是:(1)FPU处于53位模式;(2) 一个奇怪的常数将最小的double
次正常缩放到最小的long double
次正常,另一个将其向后缩放,从而处理下溢;(3) 通过内存中的double
的往返处理指数溢出。所以我猜乘法不会有双舍入。避免双舍入是必要的。目标是使浮点表达式的值独立于软件或硬件实现。在某些情况下,在某些机器上进行双舍入将无法达到此目的。啊,是的,在我的方案中,2^K2的乘法可以被通过内存的往返替换。也许这是最好的,因为2^K2太大了一点,不能用80位浮点表示:要实现“乘以2^K2”步骤,需要两个实际的浮点乘法。
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());
}
}
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 );
%}