我有一个用Java 8编写的相当简单的业余爱好项目,它在其一种操作模式中广泛使用重复的Math.round()调用.例如,一个这样的模式产生4个线程,并通过ExecutorService对48个可运行的任务进行排队,每个任务运行类似于下面的代码块2 ^ 31次:
int3 = Math.round(float1 + float2);
int3 = Math.round(float1 * float2);
int3 = Math.round(float1 / float2);
Run Code Online (Sandbox Code Playgroud)
这不完全是如何(涉及数组和嵌套循环),但你明白了.无论如何,在Java 8u40之前,类似于上述代码的代码可以在AMD A10-7700k上在大约13秒内完成大约1030亿个指令块的全部运行.使用Java 8u40,执行相同的操作大约需要260秒.代码没有变化,没有任何变化,只是Java更新.
有没有人注意到Math.round()变得慢得多,特别是当它被重复使用时?几乎就好像JVM正在进行某种优化之前它已经不再做了.也许它是在8u40之前使用SIMD而现在不是?
编辑:我已经完成了对MVCE的第二次尝试.你可以在这里下载第一次尝试:
https://www.dropbox.com/s/rm2ftcv8y6ye1bi/MathRoundMVCE.zip?dl=0
第二次尝试如下.我的第一次尝试已经从这篇文章中删除,因为它被认为太长了,并且很容易被JVM去掉代码去除(显然在8u40中发生的事情更少).
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MathRoundMVCE
{
static long grandtotal = 0;
static long sumtotal = 0;
static float[] float4 = new float[128];
static float[] float5 = new float[128];
static int[] int6 = new int[128];
static int[] int7 = new int[128];
static int[] int8 = new int[128];
static long[] longarray = …Run Code Online (Sandbox Code Playgroud) 我们知道,一些JIT允许重新排序对象初始化,例如,
someRef = new SomeObject();
Run Code Online (Sandbox Code Playgroud)
可以分解为以下步骤:
objRef = allocate space for SomeObject; //step1
call constructor of SomeObject; //step2
someRef = objRef; //step3
Run Code Online (Sandbox Code Playgroud)
JIT编译器可能会重新排序如下:
objRef = allocate space for SomeObject; //step1
someRef = objRef; //step3
call constructor of SomeObject; //step2
Run Code Online (Sandbox Code Playgroud)
即,步骤2和步骤3可以由JIT编译器重新排序.虽然这在理论上是有效的重新排序,但我无法使用x86平台下的Hotspot(jdk1.7)重现它.
那么,Hotspot JIT comipler是否可以重现任何指令重新排序?
更新:我使用以下命令在我的机器(Linux x86_64,JDK 1.8.0_40,i5-3210M)上进行了测试:
java -XX:-UseCompressedOops -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand="print org.openjdk.jcstress.tests.unsafe.UnsafePublication::publish" -XX:CompileCommand="inline, org.openjdk.jcstress.tests.unsafe.UnsafePublication::publish" -XX:PrintAssemblyOptions=intel -jar tests-custom/target/jcstress.jar -f -1 -t .*UnsafePublication.* -v > log.txt
Run Code Online (Sandbox Code Playgroud)
我可以看到该工具报告的内容如下:
[1] 5可接受对象已发布,至少有1个字段可见.
这意味着观察者线程看到了一个未初始化的MyObject实例.
但是,我没有看到像@ Ivan那样生成的汇编代码:
0x00007f71d4a15e34: mov r11d,DWORD PTR …Run Code Online (Sandbox Code Playgroud) 为什么不可能创建一个max int size的数组?
int i = 2147483647;
int[] array = new int[i];
Run Code Online (Sandbox Code Playgroud)
我找到了这个解释:
通过32位整数访问Java数组,最大理论数组大小为2147483647个元素.
但是你可以看到我的代码不起作用.创建一个大小的数组也是不可能的
new int[Integer.MAX_VALUE - 5];
Run Code Online (Sandbox Code Playgroud)
PS
为什么-5呢?
使用Collections.emptyList()或空是否存在性能差异ArrayList,尤其是在使用JIT编译器时?
我可以想象 - 例如 - JIT编译器不执行内联或静态方法调用,因为执行的方法取决于类型.
编辑
我知道Collections.emptyList()返回一个不可变列表,ArrayList而是可变对象.
我的意思是,如果我将一个或另一个作为参数传递给方法,并且该方法不修改列表,那么是否限制了JIT编译器优化方法的可能性?
一个简单的例子(只是为了澄清我的意思):
int sum(List<Integer> list)
{
int sum = 0;
for(int i=0;i<list.size();++i)
sum += list.get(i);
return sum;
}
Run Code Online (Sandbox Code Playgroud)
如果我只使用ArrayListJIT编译器调用此方法可以内联ArrayList.get().如果我也用Collections.empty()它打电话是不可能的.
那是对的吗?
如果您查看文档(例如http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html),您会发现XX的默认值为8:SurvivorRatio.
如果你在没有明确设置SurvivorRation的情况下检查一个已经启动的jvm,你可以看到这个默认值:
jmap -heap <pid>
Run Code Online (Sandbox Code Playgroud)
将输出
...
SurvivorRatio = 8
...
Run Code Online (Sandbox Code Playgroud)
但是如果使用VisualGC检查S1 S0的最大大小,则可以看到使用了SurvivorRatio = 1.
如果你明确地启动java程序-XX:SurvivorRatio = 8,图像会改变
那么为什么默认.没有使用的默认是非常恼人的.