java – 静态最终字段,静态字段和性能

即使认为这不是它的主要目的,我一直认为final关键字(在某些情况下和VM实现中)可以帮助JIT.
这可能是一个都市传奇,但我从未想过,设置场地决赛可能会对表现产生负面影响.
直到我碰到这样的代码

   private static final int THRESHOLD = 10_000_000;
   private static int [] myArray = new int [THRESHOLD];

   public static void main(String... args) {
      final long begin = System.currentTimeMillis();

      //Playing with myArray
      int index1,index2;
      for(index1 = THRESHOLD - 1; index1 > 1; index1--)
          myArray[index1] = 42;             //Array initial data
      for(index1 = THRESHOLD - 1; index1 > 1; index1--) {
                                            //Filling the array
          for(index2 = index1 << 1; index2 < THRESHOLD; index2 += index1)
              myArray[index2] += 32;
      }

      long result = 0;
      for(index1 = THRESHOLD - 1; index1 > 1; index1-=100)
          result += myArray[index1];

      //Stop playing,let's see how long it took
      System.out.println(result);
      System.out.println((System.currentTimeMillis())-begin+"ms");
   }

我们来看看:
private static int [] myArray = new int [THRESHOLD];
在W7 64位下,基于10次连续运行,我得到以下结果:

> THRESHOLD = 10 ^ 7,1.7.0u09客户端VM(Oracle):

>当myArray不是最终版时,运行时间约为2133毫秒.
>当myArray成为最终版时,运行时间约为2287毫秒.
> -server VM产生类似的数字,即2131ms和2284ms.

> THRESHOLD = 3×10 ^ 7,运行在~7647ms.
>当myArray最终时,在~8190ms运行.
> -server VM产生~7653ms和~8150ms.

> THRESHOLD = 3×10 ^ 7,1.7.0u01客户端VM(Oracle):

>当myArray不是最终版时,运行在~8166ms.
>当myArray是最终版时,运行时间约为9694ms.这差异超过15%!
> -server VM产生了可忽略的差异,有利于非最终版本,大约1%.

备注:我使用JDK 1.7.0u09的javac生成的字节码进行所有测试.除了myArray声明之外,两个版本生成的字节码完全相同,这是预期的.
那么为什么带有静态最终myArray的版本比带有静态myArray的版本慢?

编辑(使用Aubin的我的代码片段版本):

似乎版本与final关键字之间的差异与不仅仅在第一次迭代中的差异.不知何故,具有final的版本总是比没有第一次迭代时的版本慢,然后下一次迭代具有相似的时序.

例如,使用THRESHOLD = 10 ^ 8并使用1.7.0u09客户端运行时,第一次计算需要大约35秒,而第二次“仅”需要30秒.

显然,VM执行了一个优化,是JIT在运行,为什么它没有提前启动(例如通过编译嵌套循环的第二级,这部分是热点)?

请注意,我的备注对1.7.0u01客户端VM仍然有效.对于那个版本(也许是早期版本),带有final myArray的代码比没有此关键字的代码运行得慢:2671ms vs 2331ms,基于200次迭代.

最佳答案
恕我直言,不应添加System.out.println(结果)的时间,因为I / O是高度变量和耗时的.

我认为println()影响因素更大,比最终影响更大.

我建议编写性能测试如下:

public class Perf {
   private static final int   THRESHOLD = 10_000_000;
   private static final int[] myArray   = new int[THRESHOLD];
   private static /* */ long  min = Integer.MAX_VALUE;
   private static /* */ long  max = 0L;
   private static /* */ long  sum = 0L;

   private static void perf( int iteration ) {
      final long begin = System.currentTimeMillis();

      int index1,index2;
      for( index1 = THRESHOLD - 1; index1 > 1; index1-- ) {
         myArray[ index1 ] = 42;
      }
      for( index1 = THRESHOLD - 1; index1 > 1; index1-- ) {
         for( index2 = index1 << 1; index2 < THRESHOLD; index2 += index1 ) {
            myArray[ index2 ] += 32;
         }
      }
      long result = 0;
      for( index1 = THRESHOLD - 1; index1 > 1; index1 -= 100 ) {
         result += myArray[ index1 ];
      }
      if( iteration > 0 ) {
         long delta = System.currentTimeMillis() - begin;
         sum += delta;
         min = Math.min(  min,delta );
         max = Math.max(  max,delta );
         System.out.println( iteration + ": " + result );
      }
   }

   public static void main( String[] args ) {
      for( int iteration = 0; iteration < 1000; ++iteration ) {
         perf( iteration );
      }
      long average = sum / 999;// the first is ignored
      System.out.println( "Min    : " + min     + " ms" );
      System.out.println( "Average: " + average + " ms" );
      System.out.println( "Max    : " + max     + " ms" );
   }
}

而且只有10次迭代的结果是:

最后:

Min    : 7645 ms
Average: 7659 ms
Max    : 7926 ms

没有最终:

Min    : 7629 ms
Average: 7780 ms
Max    : 7957 ms

我建议读者运行此测试并发布他们的结果进行比较.

相关文章

最近看了一下学习资料,感觉进制转换其实还是挺有意思的,尤...
/*HashSet 基本操作 * --set:元素是无序的,存入和取出顺序不...
/*list 基本操作 * * List a=new List(); * 增 * a.add(inde...
/* * 内部类 * */ 1 class OutClass{ 2 //定义外部类的成员变...
集合的操作Iterator、Collection、Set和HashSet关系Iterator...
接口中常量的修饰关键字:public,static,final(常量)函数...