Brother Song wrote an article earlier and talked to everyone about the new thing AOT introduced in Spring 6 (see Spring Boot 3 new gameplay, AOT optimization! ).
After the article was published, some friends asked Brother Song if he had done a performance comparison. To be honest, this was left behind, so I will write another article today to sort out the details with my friends. When we use Native Image, Spring Boot starts In terms of parameters, how much performance has been improved?
Let me tell you the conclusion first: the startup speed is increased by more than 10 times.
1. Native Image
1.1 GraalVM
I don’t know if you guys have noticed that when we create a new Spring Boot project, there is one when adding dependencies GraalVM Native Support
. This refers to the support of GraalVM.
So what is GraalVM?
GraalVM is a high-performance general-purpose virtual machine that provides AOT compilation and binary packaging capabilities for Java applications. Binary packages based on GraalVM can achieve fast startup, ultra-high performance, no warm-up time, and require very few resources. Consumption, so there is no problem if you use GraalVM as a JVM.
In terms of operation, GraalVM supports both JIT and AOT modes:
- JIT is the abbreviation of Just-In-Time Compilation. It is a technology that dynamically compiles code into machine code while the program is running. Different from traditional static compilation (Ahead-of-Time Compilation), static compilation compiles the code into machine code before the program is executed, while the JIT compiler compiles the code fragments into machine code as needed when the program is running, and then runs it . Therefore, JIT startup will be slower because compilation requires runtime resources. We usually use the Hotspot JVM provided by Oracle to fall into this category.
- AOT is the abbreviation of Ahead-of-Time Compilation. It is a technique that statically compiles code into machine code before program execution. Unlike just-in-time compilation (JIT), just-in-time compilation dynamically compiles code into machine code while the program is running. The AOT compiler converts code into machine code during the program build or installation phase, and then directly executes the machine code at runtime without the need for a compilation process. This static compilation method can improve the startup speed and execution efficiency of the program, but it will also increase the time and complexity of building and installation. AOT compilers are usually used in the compilation process of static languages, such as C, C++, etc.
If we use AOT technology in Java applications, then our Java project will be directly compiled into machine code and can be run without the JVM, and the operating efficiency will be greatly improved.
So what is Native Image?
1.2 Native Image
Native Image is a very unique packaging technology provided by GraalVM. This packaging method can package the application into a binary package that can be run independently on the local operating system without the JVM, thus eliminating the need for JVM loading and byte processing. The warm-up time during the code running period improves the running efficiency of the program.
Native Image has the following characteristics:
- Instant startup: Native Image can achieve fast startup and instant execution because it does not require JVM startup and class loading processes.
- Reduced memory footprint: When compiled to native code, applications typically have a lower runtime memory footprint because they do not require the additional memory overhead of the JVM.
- Static Analysis: When building a Native Image, GraalVM uses static analysis to determine which parts of the application are necessary and include only those parts, which helps reduce the final executable size.
- Instant performance: While the JVM can optimize code at runtime through JIT (Just-In-Time) compilation, Native Image provides instant, pre-optimized performance, which is especially useful for applications that require fast response.
- Cross-platform compatibility: Native Image can build specific executable files for different operating systems, including Linux, macOS and Windows, that is, it automatically generates binary files that the system can execute on Mac and Linux, and automatically generates exe files on Windows. .
- Security: Because Native Image does not rely on the JVM, it reduces the attack surface for possible security vulnerabilities in the JVM.
- Interoperability with C: Native Image can integrate more easily with native C libraries because they are native code running in the same environment.
According to the previous introduction, you can also see that what GraalVM does is to compile what should be compiled before the program is run, so that when the program runs, the running efficiency will be high, and all this is done by using AOT. realized.
but! For some things involving dynamic access, GraalVM seems to be a little unable to do so. The reason is very simple. During the compilation and build period, GraalVM will use the main function as the entry point to statically analyze our code. During static analysis, some parts cannot be reached. The code will be removed, and some dynamic calling behaviors, such as reflection, dynamic proxy, dynamic attributes, serialization, class lazy loading, etc., require the program to actually run to know the result, and these cannot be identified during compilation and build. come out.
Reflection, dynamic proxy, serialization, etc. are precisely the most important things in our daily development of Java. It is impossible for us to abandon these things for Native Image! Therefore, AOT Processing is supported starting from Spring 6 (Spring Boot 3)! AOT Processing is used to complete automated Metadata collection. This collection mainly solves problems such as reflection, dynamic proxy, dynamic attributes, and dynamic calculation of conditional annotations . During the compilation and build period, it automatically collects relevant metadata information and generates configuration files, and then provides Metadata For use by the AOT compiler.
After understanding the truth, let’s experience the power of Native Image through a case!
2. Preparation
First we need to install GraalVM.
GraalVM download address:
After downloading, it will be a compressed file, unzip it, and then configure the environment variables. Everyone knows this by default, so I won’t go into details.
After GraalVM is configured, you also need to install the Native Image tool. The command is as follows:
gu install native - image
After installation, you can check the installation results by running the following command:
On the other hand, Native Image will use some C/C++ related tools when packaging, so Visual Studio 2022 also needs to be installed on the computer. For this, we only need to install the community version ( https://visualstudio.microsoft. com/zh-hans/downloads/ ):
After downloading, just double-click to install. When installing, select C++ desktop application development.
After this, the preparation work is completed.
3. Practice
Next we create a Spring Boot project and introduce the following two dependencies:
Then we develop an interface:
@RestController public class HelloController { @Autowired HelloService helloService; @GetMapping("/hello") public String hello () { return helloService.sayHello(); } } @Service public class HelloService { public String sayHello () { return "hello aot" ; } }
This is a very simple interface. Next, we package it into traditional jar and Native Image respectively.
I don’t need to say more about the traditional jar package. You can just execute mvn package:
mvn package
After packaging is completed, let’s look at the time it takes:
It doesn’t take long, about 3.7 seconds, which is relatively fast. The size of the final jar package is 18.9MB.
Let’s look at the native package and execute the following command:
mvn clean native : compile -Pnative
This packaging takes a long time, so you need to wait patiently for a while:
As you can see, it took a total of 4 minutes and 54 seconds.
When Native Image is packaged, if we are on Windows, it will be automatically packaged into an exe file. If we are on Mac/Linux, an executable file corresponding to the system will be generated.
The size of the aot_demo.exe file generated here is 82MB.
The time consumed by the two different packaging methods is not at the same level.
Let’s look at the startup time.
First look at the jar package startup time:
It takes about 1.326s.
Let’s look at the startup time of the exe file:
Boy, only 0.079s.
1.326/0.079=16.78
The startup efficiency is increased by 16.78 times!
Let me draw a table to compare these two packaging methods:
jar | Native Image | |
---|---|---|
Package size | 18.9MB | 82MB |
compile time | 3.7s | 4 minutes 54 seconds |
Start Time | 1.326s | 0.079s |
From this table we can see that Native Image takes more time to package, but once packaged successfully, the project operation efficiency is very high. Native Image very well solves the problems of Java cold start taking a long time and Java applications need to be warmed up.
Finally, you can view the compilation results when packaged into Native Image, as shown below:
Friends who have seen the Spring source code analysis by Song Ge before , this piece of code should be easy to understand. This is to directly parse the BeanDefinition. It not only registers the current Bean, but also injects the dependencies required by the current Bean. , there will be no need to parse the BeanDefinition when Spring is executed in the future.
At the same time, we can see that reflect, resource and other configuration files are generated in META-INF. These are the reflection and resource information analyzed by the native-maven-plugin plug-in we added, and are also the results of Spring AOT Processing.