/performance-final-entities

Primary LanguageJavaApache License 2.0Apache-2.0

Final Local Variables and Performance

Executive Summary

Declaring local variables as final provides better code metrics and can also improve performance, both for interpreted and compiled code.

Structural Benefits

As we all know, a final variable may only be assigned once. Declaring a variable final can serve as useful documentation that its value will not change and can help avoid programming errors. It also signals intent and promotes a better coding style, nudging away coders from mutable logic.

Performance Implications

What is less known by the general audience is that declaring a local variable as final can improve performance. The reason for this is that the java compiler will take different actions if local variables are final compared to if they are non-final. More specifically, javac will output different byte code depending on finality. This is also true for the JIT compiler (at least Oracle’s).

Java Code

Consider the following class with all the possible variants for final and non-final local variables:

package net.openhft.chronicle.finality;

public final class Methods {

    public int nfnf() {
        int ask = 42;
        int bid = 13;

        return ask - bid;
    }

    public int fnf() {
        final int ask = 42;
        int bid = 13;

        return ask - bid;
    }

    public int nff() {
        int ask = 42;
        final int bid = 13;

        return ask - bid;
    }

    public int ff() {
        final int ask = 42;
        final int bid = 13;

        return ask - bid;
    }

}

The methods are compiled using Java 8 and are subsequently analyzed using javap:

$ mvn clean install
$ cd target/classes/net/openhft/chronicle/finality/
$ javap -c Methods

Byte Code

This will produce the following output:

Compiled from "Methods.java"
public final class net.openhft.chronicle.finality.Methods {
  public net.openhft.chronicle.finality.Methods();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int nfnf();
    Code:
       0: bipush        42
       2: istore_1
       3: bipush        13
       5: istore_2
       6: iload_1
       7: iload_2
       8: isub
       9: ireturn

  public int fnf();
    Code:
       0: bipush        42
       2: istore_1
       3: bipush        13
       5: istore_2
       6: bipush        42
       8: iload_2
       9: isub
      10: ireturn

  public int nff();
    Code:
       0: bipush        42
       2: istore_1
       3: bipush        13
       5: istore_2
       6: iload_1
       7: bipush        13
       9: isub
      10: ireturn

  public int ff();
    Code:
       0: bipush        42
       2: istore_1
       3: bipush        13
       5: istore_2
       6: bipush        29
       8: ireturn
}

All local variable values are pushed onto the stack. Apparently, final variables are again pushed on the stack before the isub operations. If both variable are final, the compiler completely skips any variable loading and subtraction and directly creates a constant that is returned.

Note
The same byte code is produced by Java 15.

Benchmarking

The benchmark figures presented above are obtained using Java 15.

Running JMH tests in interpreting mode (-Xint) produces the following results:

$ mvn exec:exec@InterpretingMode
# JMH version: 1.19
# VM version: JDK 15, VM 15+36
...
# Run complete. Total time: 00:05:23

Benchmark    Mode  Cnt        Score        Error  Units
Bench.ff    thrpt   20  7330132.498 ±  75509.695  ops/s
Bench.fnf   thrpt   20  6718820.758 ± 153781.406  ops/s
Bench.nff   thrpt   20  6758901.295 ± 107004.689  ops/s
Bench.nfnf  thrpt   20  6833704.639 ± 129824.655  ops/s
Mops/s           73
          ^    +----+
          |    |    |                             68                68
   70    -+-   |    |             67            +----+          +----+
          |    |    |           +----+          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   60    -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   50    -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   40    -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   30    -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   20    -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   10    -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          +----+----+-----------+----+----------+----+----------+----+-> Type
                ff              fnf              nff            nfnf

Running JMH tests in JIT mode produces the following results (after warmup):

$ mvn exec:exec@CompiledMode
# JMH version: 1.19
# VM version: JDK 15, VM 15+36
...
# Run complete. Total time: 00:05:26

Benchmark    Mode  Cnt          Score          Error  Units
Bench.ff    thrpt   20  554199168.529 ± 14135259.828  ops/s
Bench.fnf   thrpt   20  527017952.835 ± 12746114.143  ops/s
Bench.nff   thrpt   20  535319801.891 ± 16581859.971  ops/s
Bench.nfnf  thrpt   20  534469534.590 ± 17529337.602  ops/s
Mops/s
          ^      554
          |    +----+            527             535             534
          |    |    |           +----+          +----+          +----+
   500   -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   400   -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   300   -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   200   -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
   100   -+-   |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          |    |    |           |    |          |    |          |    |
          +----+----+-----------+----+----------+----+----------+----+-> Type
                ff              fnf              nff            nfnf
Note
Running the benchmarks under Java 8 will also produce better results for final local variables.

Method Parameters

Declaring a parameter to a method final does not affect performance on the receiving end as shown hereunder:

Java Code

package net.openhft.chronicle.finality.param;

public final class Params {

    public int nfnf(int ask, int bid) {

        return ask - bid;
    }

    public int fnf(final int ask, int bid) {

        return ask - bid;
    }

    public int nff(int ask, final int bid) {

        return ask - bid;
    }

    public int ff(final int ask, final int bid) {

        return ask - bid;
    }

}

Byte Code

public final class net.openhft.chronicle.finality.param.Params {
  public net.openhft.chronicle.finality.param.Params();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int nfnf(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: isub
       3: ireturn

  public int fnf(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: isub
       3: ireturn

  public int nff(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: isub
       3: ireturn

  public int ff(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: isub
       3: ireturn
}