Explain execution? Compile and execute? Just-in-time compilation? Easily allow you to distinguish between pre-compilation and post-compilation

Understanding of compilation

"Eh, so and so, I fixed the bug yesterday. You can compile it to see if there is any problem." After that, the director of the software department took a sip of the tea in his hand. "Director, you are so awesome!...". As the subordinates worshipped their eyes, the director couldn't help but remember that he used to write code in the first place, but he was just a little white who didn't even know how to "compile".

Bring up compilation. Our initial understanding is: the program reads the written code first to determine if there are any basic grammatical errors. When the compilation is passed, it enters the run mode. Then with the deepening of learning and deepening of understanding, the understanding of compilation can no longer be limited to this.

image.png

Talking about "compiler" in the context of Java technology is actually a very vague expression without context. He may refer to the front-end compiler (the process of converting .java files into .class files through javac). Our usual development refers to "compiling the software". In fact, it refers to the front-end compiler. The back-end compiler is often ignored by us, because this matter is completely left to the JVM, and we generally care little about development.

The meaning of the so-called compilation is actually the translation of the program world. Front-end compilation means that we have to translate the .java files we know into the .class files recognized by the JVM. But the computer does not recognize your .class file. Therefore, the JVM needs to translate the .class files it recognizes into the relevant binary machine code, which is the so-called back-end compilation.

Now finally there is a more specific concept of compilation.

Back-end compilation and optimization

If the bytecode is regarded as the intermediate expression form of the program, then no matter when and in which state the compiler converts the class file into another binary machine code for the computer, it can be regarded as a back-end compilation process.

Back-end compilation mainly includes Just In Time and Ahead Of Time. Below we will introduce these two forms respectively.

Interpretation and execution

Interpretation and execution must be placed before compilation and execution. Because there is no accident, in most cases, the processing procedures are explained and executed.

Through the interpreter, when the code is executed, it is translated into machine code one by one without saving. The interpreter method is very inefficient. It needs to translate the bytecode into machine code before it can be executed. (Explanation and execution at the same time). However, except for the pre-compiled program in the program, the code starts from the interpretation and execution at the beginning.

Compile and execute

Under certain conditions, the JVM itself will feel that the interpretation and execution of ink is endless. So at this time JVM workers thought, since this code is often translated into machine code anyway, I simply compile these codes into machine code in advance, and then just read the machine code directly when the code is run?

This resulted in compilation and execution. In certain scenarios or through certain configurations, the virtual machine can compile part of the code into machine code. After being compiled into machine code, each time it runs until now, it is directly executed with machine code to improve operating efficiency.

Just-in-time compilation (JIT)

Assuming that the program does not have any pre-compilation, then the program will only be interpreted and executed from the beginning.

But let's imagine it, suppose we are an English teacher. We teach a student how to pronounce English words. One time, two times... This student still doesn't know how to read when it comes to the 100th and 1000th times. Are you a little annoyed? Yes, the virtual machine is also annoying (to be precise, the developers of the virtual machine found this problem). Therefore, the concept of hot code is proposed.

If a program always repeatedly executes a high-frequency program block, it will be judged as a hot code at this time. So the code with such a high repetitive execution rate, don’t translate it every time, just translate it once and read the translated content every time.

Compile the hotspot code into machine code related to the local platform and save it to the memory. Compilation and execution only need to be compiled once offline, while interpretation and execution need to be compiled each time, so the compilation and execution are higher in operating efficiency.

The HopSpot virtual machine adopts a hybrid mode , which combines the advantages of interpretation execution and just-in-time compilation. It will first execute the interpretation and execution of the bytecode , and then compile the hotspot code that is repeatedly executed in this method as a unit for instant compilation.

Just-in-time compilation is based on the assumption that the program complies with the 28th law, that is, 20% of the code occupies 80% of the computer resources.

For most of the infrequently used codes, we don’t need to spend time compiling them into machine code, but run in an interpreted execution mode; on the other hand, for the hot code that only occupies a small part, we can compile them into machine code , Has reached the ideal operating speed.

 

In theory, the execution efficiency of a Java program after just-in-time compilation may exceed that of a C++ program. This is because compared with static compilation (all translated into machine code at the beginning) , just-in-time compilation has information about the program at runtime (the program can be optimized again during runtime) , and can make corresponding decisions based on this information Optimization, better peak performance.
Oracle's HotSpot VM comes with two JIT compilers implemented in C++: C1 and C2.

Introduction to C1 and C2 compilers

After the previous explanation of just-in-time compilation and pre-compilation. There should be a rough idea now. Although the goal of the compiler is to translate the program code into the local machine code, the difficulty is not whether the machine code can be successfully translated. The output code optimization quality is the key to determining whether the compiler is good or not.

C1 (Client Compiler)

The client-side compiler translates .class files conservatively, but it is very fast.

And why is it accompanied by this feature? It is because the C1 compiler does not apply radical optimization techniques, because these optimizations are often accompanied by time-consuming code analysis.

 

C2 (Server Compiler)

The server-side code compiler uses high-complexity optimization algorithms. Radical optimization techniques are often adopted, so the execution speed is slower, but the optimized code execution efficiency is higher.

C2 can be imagined as our artificial intelligence. According to the behavioral habits of the program during the operation period, the optimization of the predictive nature is made. This optimization can compile more efficient code.

Before Java 7, users need to choose a suitable JIT compiler according to their own application scenarios. For example, C1 is used for GUI client programs that prefer high startup performance, and C2 is used for server-side programs that prefer high peak performance.

 

Significance of mixed use of interpreter and compiler

The interpreter and the compiler have their own advantages directly:

1. When the program needs to be started quickly, the interpreter can play a role first, saving the time of compilation and running immediately.

(Although both are translated into machine code, it will inevitably be more time-consuming because the compiler has to optimize and save the code)

2. After the program has been running for a period of time, the compiler gradually takes effect. Put more and more hot codes

Translated into machine code. This can reduce the time consumption of many interpreters and improve efficiency. (There is also a little space to change the taste of time)

3. When the memory resource limit in the program running environment is large, interpreted execution can be used to save memory. Otherwise, you can use

Translator improves efficiency.

4. At the same time, the interpreter can also be used as a backup "escape door" after the compiler's radical optimization (If the situation runs, HotSpot

The virtual machine will also use the client-side compiler without radical optimization to act as the "escape door" role). Why do you need an escape door? Because in the process of running, the compiler may recognize some scenes and optimize for speed-up based on experience. However, these scenarios are not guaranteed to be correct. At this time, the hypothesis of radical optimization will not hold. For example, if a new class is loaded, the inheritance structure of the class will change, causing a "rare trap". At this point, you can re-interpret and execute the compiled and executed code through de-optimization. This also tells us why the HotSpot virtual machine is a virtual machine coexisting with an interpreter and a compiler.

Implementation details of the interpreter and compiler of layered compilation

Although not all Java virtual machines adopt a running architecture in which an interpreter and a compiler coexist. But the current mainstream commercial Java virtual machine. For example, HotSpot, OpenJ9, etc., both include an interpreter and a compiler internally.

Because the just-in-time compiler compiles this code, it takes up time for the program to run. Generally, the more optimized code is compiled, the longer it will take. And if you want to compile more optimized code, the interpreter may have to collect performance monitoring information for the compiler. The interpreter separates the experience to help the compiler, so its efficiency is also affected. In order to achieve the best balance between the startup response speed and operating efficiency of the program, HotSpot has added the function of layered compilation when compiling the subsystem. These include:

level 0: interpreter interpretation and execution
level 1: C1 compilation, no profiling (performance monitoring function)
level 2: C1 compilation, only profiling ((performance monitoring function)) of method and loop back-edge execution times
level 3: C1 compilation, except The profiling in level 2 also includes branch (for branch jump bytecode) and receiver type (for member method calls or class detection, such as checkcast, instnaceof, aastore bytecode) profiling
level 4: C2 compilation

To explain here, profiling refers to the collection of data that can reflect the execution status of the program during the execution of the program. The data collected here is called the profile of the program.

 

1. Under normal circumstances, a method is first interpreted and executed (level 0), then compiled by C1 (level 3), and then compiled by C2 with profile data (level 4)
2. If the compiled object is very simple, the virtual machine thinks There is no difference between compiling through C1 or compiling through C2, it will be directly compiled by C1 without inserting profiling code (level 1)
3. When C1 is busy, the interpreter will trigger profiling, and then the method will be directly compiled by C2;
4. When C2 is busy, the method will be compiled by C1 first and keep less profiling (level 2) to obtain higher execution efficiency (30% higher than level 3).

Under normal circumstances, the execution efficiency of C2 code is more than 30% higher than that of C1 code. However, for the three states of C1 code, the order of execution efficiency from high to low is 1 level> 2 level> 3 levels.

The performance of layer 1 is slightly higher than that of layer 2, and the performance of layer 2 is 30% higher than that of layer 3. This is because the more profiling, the greater the additional performance overhead.

After implementing hierarchical compilation, the interpreter, client-side compiler, and server-side compiler will work at the same time. Hot code may be compiled multiple times. Use the client-side compiler to get higher compilation speed, and use the server-side compiler. To obtain better compilation quality, there is no need to undertake the additional task of collecting performance monitoring information during interpretation and execution. When the server-side compiles and uses high-complexity optimization algorithms, the client-side compiler can first use simple optimizations for it Fight for more compilation time.

Hot code judgment

During operation, the target that will be compiled by the just-in-time compiler is the "hot code". The "hot code" here includes:

1. Methods that are called multiple times

2. Loop body that is executed multiple times

To know whether a certain piece of code is a hot code and whether it needs to trigger real-time compilation, this behavior is called "Hot Spot Code Detection" (Hot Spot Code Detection). There are currently two mainstream methods:

(1) Sample Based Hot Spot Code Detection: periodically check the top of the call stack of each thread, if it is found that a certain (or some) method often appears on the top of the stack (the method on the top of the stack is The method to be executed), then this method is the "hot method". The advantage is that it is efficient, and it is easy to obtain the relationship of method calls (just unfold the call stack). The disadvantage is that it is not accurate enough and is easily affected by thread blocking and external factors.

(2) Counter Based Hot Spot Code Detection: Create a counter for each method (even code block) to count the number of executions of the method. If the number of executions exceeds a certain threshold, it is considered a "hot spot method" . The disadvantage is that it is more troublesome, you need to maintain a counter for each method, and you cannot directly obtain the calling relationship. The advantage is that it is more rigorous and accurate.

 

Method call counter

Used to count the number of times the method is called. When a certain number of times is reached, it can be judged as a hot code.

When a method is called, the virtual machine first checks whether the method has a just-in-time compiled version, and if it exists, it will preferentially use the compiled native code for execution. If there is no compiled version, the method call counter value is incremented by one, and then it is judged whether the sum of the method call counter and the backside counter value exceeds the threshold value of the method call counter. Once the threshold is exceeded, a code compilation request for this method will be submitted to the just-in-time compiler. The process is shown in the figure below:

image.png

When a certain time limit is exceeded , if the number of method calls is still not enough for it to be submitted to the just-in-time compiler, the method call counter will be halved. This process is called the counter decay of the method call. This period of time is called the Counter Half Life Time (Counter Half Life Time) of the method. You can use the virtual machine parameter -XX:-UseCounterDecay to turn off heat decay. In addition, you can use the -XX:CounterHalfLifeTime parameter to set the half-life cycle time, in seconds.

Back-to-edge counter

Used to count the number of loop body codes. When a certain number of times is reached, it can be judged as a hot code. (For, while, etc.).

When an interpreter encounters a back-side instruction, it will first check whether the executable code fragment has a just-in-time compiled version, and if it exists, it will preferentially use the compiled native code for execution. If there is no compiled version, the value of the return counter is increased by one, and then it is judged whether the sum of the method call counter and the value of the return counter exceeds the threshold of the method call counter. If the threshold is exceeded, a stack replacement compilation request will be submitted, and the value of the backside counter will be slightly reduced, so as to continue to execute the loop in the interpreter and wait for the compiler to output the compilation result. The process is shown in the figure below:

image.png

Unlike the method counter, the edge-back counter does not have a process of decay by half .

 

Guess you like

Origin blog.csdn.net/weixin_47184173/article/details/109706816