[Java study notes (113)] compiler optimization (method inlining, escape analysis, common sub-expression elimination, array boundary checking)

This article is published by the official account [Developing Pigeon]! Welcome to follow! ! !


Old Rules-Sister Town House:

One. Compiler optimization techniques

(I. Overview

       The goal of the compiler is to translate the program code into local machine code. The quality of output code optimization is the key to determining whether the compiler is good or not. All optimization methods are based on the intermediate representation of the code or on the machine code. Instead of Java source code.

(Two) method inlining

1 Overview

       Method inlining is the most important optimization method of the compiler, because in addition to eliminating the cost of method calls, it is more important to establish a good foundation for other optimization methods. Its optimization purpose is to copy the code of the target method intact to the method that initiated the call to avoid real method calls.


2. Java method inlining

       Since Java's default instance method is a virtual method, it is necessary to make a polymorphic selection of method receivers at runtime. There may be more than one version of the method receiver, only private methods that can also be invoked by the invokespecial instruction, the instance constructor, The parent class method and the static method called using the invokestatic instruction will be parsed by the compiler.

       For virtual methods, it is difficult for the compiler to statically inline which method version should be used. In order to solve the contradiction between virtual methods and inlining, the Java virtual machine introduces type inheritance analysis (CHA) to determine that it has been loaded at present In the class, whether a certain interface has more than one implementation, whether a certain class has subclasses, whether a certain subclass covers a certain virtual method of the parent class, etc. In this way, the compiler will process the inlining according to different situations. If it is not a virtual method, it will directly inline; if it is a virtual method, it will query CHA whether the method has multiple method versions in the current state, if Only one version is inlined using the current method. This becomes a guardian inline. However, since Java is dynamically connected, it may be loaded into a new type at any time to change the conclusion of CHA. Therefore, it is necessary to leave an escape door for this inline. When a new loaded class appears, you must discard the compiled code, return to the interpreted state for execution, or recompile.

       If the CHA query reveals that there are multiple method versions to choose from, use inline caching to reduce the cost of method calls. In this state, method calls really happen, but it is faster than directly querying virtual methods. Inline The cache is a cache built before the normal entry of the target method, records the version information of the method receiver, and compares the receiver's version each time the method is called. If the method version of each subsequent call is the same, it is a monomorphic inline cache. Through this cache, there is only more sequential type judgment overhead than non-virtual method calls that are not inlined; if the method receivers are inconsistent, it will degenerate. To become a super polymorphic inline cache, the overhead is equivalent to looking up the virtual method table for method dispatch.

(Three) escape analysis

1. Principle

       Escape analysis is not a means of directly optimizing code, but an analysis technique that provides a basis for other optimization measures. The basic principle is to analyze the dynamic scope of an object. When an object is defined in a method, it may be referenced by an external method, for example, as Calling parameters are passed to other methods, which is called method escape; it may even be accessed by external threads, such as assigning values ​​to instance variables that can be accessed in other threads. This is called thread escape. Never escape, method escape to thread escape, which is called the different escape degrees of the object from low to high. Depending on the degree of escape, different degrees of optimization will be taken on this object instance.

2. Allocation on the stack

       The objects in the Java heap are shared and visible to all threads. Whether the objects on the heap are marked as recyclable objects or memory reclaimed, it consumes a lot of resources. If it is determined that an object will not escape from the thread and let the object allocate memory on the stack, the memory will be destroyed as the stack frame is popped, reducing the pressure on the garbage collection system. The allocation on the stack supports method escape, but does not support thread escape. .

3. Scalar substitution

       If a piece of data can no longer be decomposed, for example, the original data type in the Java virtual machine can be called a scalar; relatively, the data that can be decomposed is called an aggregate amount, and an object in Java is an aggregate amount. If a Java object is broken up, According to the situation of program access, the member variables used in it are restored to the original type for access. This process is scalar substitution.

       Adding escape analysis can prove that an object will not be accessed by the method, and the object can be disassembled, then the program may not create this object when the program is actually executed, but directly create its several member variables used by this method instead. These member variables can be allocated on the stack, and can also create conditions for subsequent optimization methods. Scalar substitution does not allow the object to escape the scope of the method, that is, the method escapes.

4. Synchronization Elimination

       If the escape analysis determines that a variable will not escape the thread and cannot be accessed by other threads, then there will be no competition for reading and writing of this variable, and synchronization measures can be safely eliminated.

(4) Elimination of common subexpressions

       If an expression has been calculated, and the value has not changed, the expression is called a common sub-expression this time, and the original value can be used directly. If this kind of optimization is limited to the basic blocks of the program, it is called local common sub-expression elimination; if the scope of optimization covers multiple basic blocks, it is called global common sub-expression elimination.


(5) Elimination of array boundary check

       In the Java language, the upper and lower bounds of the array are checked, otherwise a runtime exception will be thrown, which is very friendly to programmers and avoids overflow attacks. But for the execution subsystem of the virtual machine, every time an array element is read and written, there is an implicit conditional judgment operation, which is a performance burden.

       Java's various security checks, such as array out-of-bounds, null pointer access, divisor 0, etc. will have exception checks, and these checks will contain implicit overhead. In order to eliminate these implicit overheads, such as array boundary check optimization, the runtime check will be as early as possible to the compiler to complete, or implicit exception handling, such as the exception handler at the process level, as long as the array is not empty, it will not There is additional overhead for null judgment, but once the array is empty, it needs to be transferred to the exception handler, which is much slower than a null judgment check. Model, HotSpot virtual machine automatically selects the appropriate solution based on the performance monitoring information collected during the runtime.

Guess you like

Origin blog.csdn.net/Mrwxxxx/article/details/112972500