java循环长度的相同、循环体代码相同的两次for循环的执行时间相差了100倍?
非常感谢 @杨博 的提示,我把相关JIT编译内容和内联情况打印出来了,验证了他的“ new Integer是否被内联可能是影响性能的关键因素 ”的猜测。程序执行时加入下面参数:
-XX:AutoBoxCacheMax=8000
-XX:+UnlockDiagnosticVMOptions //后面2个参数的需要
-XX:+PrintCompilation //打印JIT编译详情
-XX:+PrintInlining //打印内联详情
当程序的循环长度小于AutoBoxCacheMax的的参数值时,即时编译结果如下:
com.emc.finaly.FinallyTest::main @ 36 (93 bytes)
@ 46 java.lang.Integer::valueOf (32 bytes) inline (hot)
@ 68 java.util.Date::<init> (8 bytes) call site not reached
@ 71 java.util.Date::getTime (5 bytes) inline (hot)
@ 1 java.util.Date::getTimeImpl (27 bytes) executed
< MinInliningThreshold times
@ 14 java.util.Date::<init> (8 bytes) call site not reached
@ 17 java.util.Date::getTime (5 bytes) inline (hot)
@ 1 java.util.Date::getTimeImpl (27 bytes) executed
< MinInliningThreshold times
当程序的循环长度大于AutoBoxCacheMax的参数值时,即时编译结果如下:
@ 46 java.lang.Integer::valueOf (32 bytes) inline (hot)
@ 28 java.lang.Integer::<init> (10 bytes) inline (hot)
@ 1 java.lang.Number::<init> (5 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 68 java.util.Date::<init> (8 bytes) inline (hot)
@ 1 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
@ 4 java.util.Date::<init> (10 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 71 java.util.Date::getTime (5 bytes) inline (hot)
@ 1 java.util.Date::getTimeImpl (27 bytes)
executed < MinInliningThreshold times
@ 14 java.util.Date::<init> (8 bytes) inline (hot)
@ 1 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
@ 4 java.util.Date::<init> (10 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 17 java.util.Date::getTime (5 bytes) inline (hot)
@ 1 java.util.Date::getTimeImpl (27 bytes)
executed < MinInliningThreshold times
两次编译的结果最大的差别在于对Integer以及父类构造函数的内联。对于是否使用range与否,其实本质上没有多大关系,根本在于JIT。问题到了这里似乎已经可以完结了,现在我就一个问题,内联为什么花了1秒钟这么长时间。明天做个测试,回头再更新。
============================================================
怕有些童鞋不明白我的问题是什么,前排解释一下概念和所做的工作吧。因为这些内容写在题目里面太长了。。所以就单独提出来写成答案了,下班的时候完善一下我之前做的测试和测试现象。
拆箱与装箱:jvm把一个包装类型自动转成基本数据类型叫做拆箱,反之叫做装箱。例如:
Integer integer = 2 ;//装箱 实际运行代码为Integer integer = Integer.valueOf(2);
int i = integer ;//装箱 实际运行代码为int i = integer.intValue();
Integer缓存:程序在执行装箱操作时,实际执行的代码为Integer.valueOf(number);我们查看这个方法的源码,发现number在一定范围内不创建新的对象,而是直接拿缓存。
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
low为固定值-128,high可以配置,默认值为127。最高位可以用 AutoBoxCacheMax=XXX来配置。
在加载IntegerCache类时,完成对IntegerCache.high的初始化。
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
事实上,不仅仅Integer有缓存机制,其他包装类型也有缓存操作也有缓存,只不过只有Integer可以配置上限。
最初我的问题是想看看因为缓存带来性能的提升,所以设置了 AutoBoxCacheMax = 8000选项,使用多层for循环装箱操作。下面看看我做的几个这方面的测试吧。
1.设置 AutoBoxCacheMax = 8000,当装箱数据小于8000,例如7999
无论是用range还是写死,程序都能在30毫秒内结束,
2.AutoBoxCacheMax不做设置,使用默认缓存大小,程序消耗时间在1700毫秒。
以上两组说明了缓存对程序效率的提升是巨大的。
3.设置 AutoBoxCacheMax = 8000,装箱数范围为【0,8000】在程序中写死8000。
程序执行时间为问题中的1700.
What The Fuck ??????仅仅增加了1,程序执行时间增加了几百倍。7999之前都能在16毫秒内完成,看样8000是质变。会不会在装箱8000的时候非常耗时呢??于是我把第三层循环去掉
for(int k = 0 ;k<10;k++){
long time = new Date().getTime();
for(int j=0;j<10000;j++){
//for(int i = 0;i<8000 - 1 ;i++){
Integer integer = 8000 ;
// }
}
sum = sum + new Date().getTime() - time;
}
无论8000换成8001、8100还是其他数,程序多次执行时间为16毫秒左右。对8000的装箱看样没有特殊性。我还是不死心,把8000分成两段执行,一段【0,7998】一段【7998,8001】
程序如下:
for(int k = 0 ;k<10;k++){
long time = new Date().getTime();
for(int j=0;j<10000;j++){
for(int i = 0;i<8000 - 2 ;i++){
Integer integer = i ;
}
for(int i = 7998;i<8000 + 1 ;i++){
Integer integer = i ;
}
}
sum = sum + new Date().getTime() - time;
}
相同的执行长度,程序执行时间为38毫秒。
初段结论:
for循环、AutoBoxCacheMax存在某种联系。但是能力有限,不知道联系是什么。