Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static final field, static field and performances

Even thought it's not its main purpose, I've always thought that the final keyword (in some situations and VM implementations) could help the JIT.
It might be an urban legend but I've never imagined that setting a field final could negatively affect the performances.

Until I ran into some code like that:

   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");
   }


Let's have a look at: private static int [] myArray = new int [THRESHOLD];
Under W7 64-bit and on a basis of 10 successive runs, I get the following results:

  1. THRESHOLD = 10^7, 1.7.0u09 client VM (Oracle):

    • runs in ~2133ms when myArray is not final.
    • runs in ~2287ms when myArray is final.
    • The -server VM produces similar figures i.e 2131ms and 2284ms.

  2. THRESHOLD = 3x10^7, 1.7.0u09 client VM (Oracle):

    • runs in ~7647ms when myArray is not final.
    • runs in ~8190ms when myArray is final.
    • The -server VM produces ~7653ms and ~8150ms.

  3. THRESHOLD = 3x10^7, 1.7.0u01 client VM (Oracle):

    • runs in ~8166ms when myArray is not final.
    • runs in ~9694ms when myArray is final. That's more than 15% difference !
    • The -server VM produces a neglectable difference in favour of the non-final version, about 1%.

Remark: I used the bytecode produced by JDK 1.7.0u09's javac for all my tests. The bytecode produced is exactly the same for both versions except for myArray declaration, that was expected.

So why is the version with a static final myArray slower than the one with static myArray ?


EDIT (using Aubin's version of my snippet):

It appears that the differences between the version with final keyword and the one without only lies in the first iteration. Somehow, the version with final is always slower than its counterpart without on the first iteration, then next iterations have similar timings.

For example, with THRESHOLD = 10^8 and running with 1.7.0u09 client the first computation takes approx 35s while the second 'only' takes 30s.

Obviously the VM performed an optimization, was that the JIT in action and why didn't it kick earlier (for example by compiling the second level of the nested loop, this part was the hotspot) ?

Note that my remarks are still valid with 1.7.0u01 client VM. With that very version (and maybe earlier releases), the code with final myArray runs slower than the one without this keyword: 2671ms vs 2331ms on a basis of 200 iterations.

like image 774
Jerome Avatar asked Oct 06 '22 03:10

Jerome


1 Answers

IMHO, the time of the System.out.println( result ) should not be added because I/O are highly variables and time consuming.

I think the factor of println() influence is bigger, really bigger than final influence.

I propose to write the performance test as follow:

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" );
   }
}

And the results of only 10 iterations are:

With final:

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

Without final:

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

I suggest that readers run this test and post their results to compare.

like image 184
Aubin Avatar answered Oct 10 '22 04:10

Aubin