5

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

直到我遇到这样的代码:

   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 次的基础上,我得到以下结果:

  1. THRESHOLD = 10^7, 1.7.0u09 客户端虚拟机 (Oracle):

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

  2. THRESHOLD = 3x10^7, 1.7.0u09 客户端虚拟机 (Oracle):

    • myArray当不是最终结果时,运行时间约为 7647 毫秒。
    • 最终运行时间约为 8190 毫秒myArray
    • -server VM 产生 ~7653ms 和 ~8150ms。

  3. THRESHOLD = 3x10^7, 1.7.0u01 客户端虚拟机 (Oracle):

    • myArray当不是最终状态时,运行时间约为 8166 毫秒。
    • 最终运行时间约为 9694 毫秒myArray。相差15%以上!
    • -server VM 对非最终版本产生了可忽略的差异,大约 1%。

备注:我使用 JDK 1.7.0u09 的 javac 生成的字节码进行所有测试。两个版本生成的字节码完全相同,除了myArray声明,这是预期的。

那么为什么版本static final myArray比带有慢的版本static myArray呢?


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

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

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

显然,VM 执行了优化,是 JIT 在起作用吗?为什么它不早点启动(例如通过编译嵌套循环的第二级,这部分是热点)?

请注意,我的评论对于 1.7.0u01 客户端 VM 仍然有效。使用那个版本(可能还有更早的版本),代码final myArray运行速度比没有这个关键字的代码慢:2671ms vs 2331ms,基于 200 次迭代。

4

1 回答 1

4

恕我直言,不应添加 System.out.println( result ) 的时间,因为 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

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

于 2012-11-01T08:13:14.270 回答