Saturday, February 14, 2015

GBench 0.4.3 released - Let's benchmark "as" operator in Groovy 2.4 and 2.3.

Today, I released version 0.4.3 of GBench, the benchmarking module for Groovy.

The release is a maintenance release, but it includes a new feature, anonymous (unlabeled) code block syntax. The syntax makes your benchmarking code simpler when you have only one code block to be benchmarked.

// benchmark {
//   label {
//     // code to be benchmarked
//   }
// }

benchmark {
  // code to be benchmarked
}

Let's benchmark using the syntax. This time's target is "Optimization of primitive type conversions with the as operator (GROOVY-7140)". I wrote a the following benchmarking code:

// Bench.groovy
import groovy.transform.CompileStatic

@CompileStatic
void test(char x) {
    ((((x as byte) as short) as int) as long)
}

benchmark {
    test('a')
}.prettyPrint()

Here are the results of the code above with Groovy 2.3.0 and 2.4.0 in my environment.

$ grape install org.gperfutils gbench 0.4.3-groovy-2.3
$ gvm use groovy 2.3.0
$ groovy -cp `find ~/.m2 -name gbench-0.4.3-groovy-2.3.jar` Bench.groovy
Environment
===========
* Groovy: 2.3.0
* JVM: Java HotSpot(TM) 64-Bit Server VM (24.75-b04, Oracle Corporation)
    * JRE: 1.7.0_75
    * Total Memory: 491.5 MB
    * Maximum Memory: 910.5 MB
* OS: Mac OS X (10.10.1, x86_64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

   user  system   cpu  real

   1822       3  1825  1828
$ grape install org.gperfutils gbench 0.4.3-groovy-2.4
$ gvm use groovy 2.4.0
$ groovy -cp `find ~/.m2 -name gbench-0.4.3-groovy-2.4.jar` Bench.groovy
Environment
===========
* Groovy: 2.4.0
* JVM: Java HotSpot(TM) 64-Bit Server VM (24.75-b04, Oracle Corporation)
    * JRE: 1.7.0_75
    * Total Memory: 491.5 MB
    * Maximum Memory: 910.5 MB
* OS: Mac OS X (10.10.1, x86_64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

   user  system   cpu  real

   1543       3  1546  1549

It seems no doubt that the performance of "as" operator was improved in Groovy 2.4.0. So how did they make it? The difference between the following bytecode snippets is the answer.

Groovy 2.3.0

  public void test(char);
    Code:
       0: iload_1
       1: invokestatic  #74                 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
       4: getstatic     #80                 // Field java/lang/Byte.TYPE:Ljava/lang/Class;
       7: invokestatic  #84                 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.asType:(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
      10: invokestatic  #90                 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.byteUnbox:(Ljava/lang/Object;)B
      13: invokestatic  #93                 // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
      16: getstatic     #96                 // Field java/lang/Short.TYPE:Ljava/lang/Class;
      19: invokestatic  #84                 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.asType:(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
      22: invokestatic  #100                // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.shortUnbox:(Ljava/lang/Object;)S
      25: invokestatic  #103                // Method java/lang/Short.valueOf:(S)Ljava/lang/Short;
      28: getstatic     #106                // Field java/lang/Integer.TYPE:Ljava/lang/Class;
      31: invokestatic  #84                 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.asType:(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
      34: invokestatic  #110                // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.intUnbox:(Ljava/lang/Object;)I
      37: invokestatic  #113                // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      40: getstatic     #116                // Field java/lang/Long.TYPE:Ljava/lang/Class;
      43: invokestatic  #84                 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.asType:(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
      46: invokestatic  #120                // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.longUnbox:(Ljava/lang/Object;)J
      49: pop2
      50: return

Groovy 2.4.0

  public void test(char);
    Code:
       0: iload_1
       1: i2b
       2: i2s
       3: i2l
       4: pop2
       5: return