java循环长度的相同、循环体代码相同的两次for循环的执行时间相差了100倍?

我实际想看看Integer缓存对程序的提升效果。却发现这种情况。 测试代码如下: public static void main(String[] a…
关注者
356
被浏览
81,129
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

非常感谢 @杨博 的提示,我把相关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存在某种联系。但是能力有限,不知道联系是什么。