JVM JIT optimization techniques.

Deferred compilation

The Java source compiler (javac) reads Java source files and compiles them into class files. Unlike many other compilers, like gcc or ghci it does not produce performant native code for a given target architecture, but it creates bytecode. The bytecode is composed from a portable instruction set, where all operation codes are represented in a single byte. The instructions are executed on a virtual machine. Thanks to this, the bytecode is architecture agnostic, but it’s also a lot less performant in itself than the optimized native code.

 The other difference from the compilers mentioned above is that the bytecode compilation does not involve much optimization just a few, like inlining referred constants. (This can bite you if you dare to patch class files directly.) This means that the produced bytecode resembles the original characteristics of the source code.

This similarity opens up the possibility of decompiling applications with ease, and simplifies bytecode manipulation, which is great, for example, if you decide to collect the variable assignments for failing tests. Early JVM versions did not have any further optimizations, they worked with the bytecode by interpreting the opcodes directly. To make things more performant, HotSpot was introduced in JDK 1.3 to provide Just-In-Time compilation, enabling the runtime to execute optimized code rather than slowly interpreting bytecode instructions.


The JVM takes the time to carefully watch the code as it executes on the virtual machine. For example it analyses what parts of the code are executed often to decide what is worth the optimization, and it also analyses how the code is executed to decide what optimizations can be applied.
The JIT Compiler produces optimized code tailored to the target architecture that replaces the interpreted version on the fly. The compilation happens on a background thread, so it doesn’t really interrupt the program execution.
Some optimizations require a more detailed profile, some of them not, so some optimizations can be applied earlier than others, and some of them only worth the effort if the application will run for a long time.
There are two compilers in the JVM: the client (c1) and the server (c2) compiler.
The client compiler performs simpler optimizations, so it requires less time to analyse the running code. It’s best used for relatively short running client applications, providing faster startup, but less optimization. The server compiler targets applications that run for a long time, collects more data for profiling. It starts slower, but offers more advanced optimization techniques.
With Java 6, one had to choose between c1 and c2 compilers. From Java 7, there is an option to use Tiered compilation, and from Java 8 this is the default behaviour of the JVM. This approach uses both the c1 and the c2 compilers.
Lock Coarsening
Where Lock Elision is not applicable, Lock Coarsening might kick in to improve the performance. This optimization merges adjacent synchronization blocks where the same lock object is used, thus reducing synchronization overhead
Dead Code Elimination
Dead Code is code that has no effect on the outcome of the program. In many cases, such code can be detected and removed without any noticeable behaviour change, except performance.
Consider the following code:


public void deadCodeElim() {
 int size = 20000;
 int[] values = createValues(size);
 
 int result = 0;
 for (int value : values) {
  result += value;
 }
 
 // We don't do anything with the result
}

public int[] createValues(int size) {
 int[] values = new int[size];
 for (int i = 0; i < values.length; i++) {
  values[i] = i;
 }
 return values;
}

If we forget to do anything with the computed result in the end of the deadCodeElim method, then it’s futile to initialize and increment it with every value. But if we don’t use the result for anything, then it means that we also don’t use the values array for anything, so the call to the createValues method can be skipped as well. Which makes the size variable completely useless. The JVM removes all these unnecessary code as part of this optimization making the implementation much faster.


Reference:

Source: https://advancedweb.hu/2016/05/27/jvm_jit_optimization_techniques/

Kommentare

Beliebte Posts