This is the third day of my August challenge.
So on this basis, the real-time compiler also made a lot of optimization measures, so as to further improve performance. In this lesson we will look at method inlining, which is one of the optimizations for the just-in-time compiler.
Methods the inline
There are many techniques for code optimization, and implementing them can be difficult, but most of them are fairly easy to understand. For the sake of introduction, let’s start with a simple piece of code to see how the virtual machine implements method inlining code optimization.
public class InlineDemo { private static int add1(int x1,int x2,int x3,int x4){ return add2(x1,x2)+add2(x3,x4); } public static int add2(int x1,int x2){ return x1+x2; }}Copy the code
The code above is very simple, calling the ADD2 method twice inside the add1 method. How do you think the compiler will optimize this code?
We know that calling a method involves pushing an element onto the stack as it enters the method, and popping that element off the stack as it returns.
For example, when you push a stack, you have to store data in the stack, so there is an overhead of memory. At the same time the stack and out of the stack are also need time, so there is time overhead.
If the code is not called many times, the overhead of pushing and unloading the stack doesn’t matter. But assuming the code is called very frequently, say 20,000 times per second, the cumulative overhead is fairly substantial.
Is there any way to optimize? Of course there is, the JVM automatically recognizes hot methods and automatically inlines methods. Method inlining is copying the target method’s code into the calling method to avoid the actual method call. For example, after the method is inlined, as in the code above. The inline optimized code looks like this:
private static int addInline(int x1,int x2,int x3,int x4){
return x1+x2+x3+x4;
}
Copy the code
This means that add1 and ADD2 methods are automatically merged together to reduce the operation of pushing the stack and pushing the stack.
Method inlining is more important than other optimization measures. The purpose of method inlining is mainly two, one is to remove the cost of method invocation (such as building stack frames), and the other is to establish a good foundation for other optimization. After method inlining expansion, it is convenient to adopt subsequent optimization means in a wider range, so as to obtain better optimization effect. Therefore, compilers of all kinds tend to put inline optimizations first in the optimization sequence.
Method inline conditions
Note, then, that using methods to inline it is conditional:
If the method body is too large, the JVM will not inline it. By default, hot methods will be inlined if the method body is less than 325 bytes. You can also use this parameter: -xx :FrequentInlineSize modifies the method size. How the JVM finds hot methods was discussed in detail last time. For non-hot methods, if your method body is less than 35 bytes, you will also try to inline the method. You can also change the method size by using -xx :MaxInlineSize, which is the first condition for method inlining.
Second: the method being called can be uniquely identified at runtime so that the method can be inlined. For static, private, and final modified methods, JIT can uniquely determine the implementation. However, for instance methods decorated with public, the implementation it points to may be either the method itself or a method implementation of a parent or subclass, because of polymorphism. In this case, inlining is only possible if the real-time compiler can uniquely determine the implementation of the method. This is the second condition for method inlining.
Method main point of inlining
According to these two conditions, we can sum up several points of attention when using method inlining:
First: when we write code, we should try to avoid writing a lot of code in a method, so that the method body as small as possible.
Second, we should try to use final, private static, and other keyword-modifier methods to avoid extra type checking on methods because of polymorphism, and probably find that there is no way to inline methods because there is no way to uniquely determine the implementation of the method.
Third: In some scenarios, you can set JVM parameters to reduce the threshold of hot spots or modify the threshold of method body size to allow more methods to be inlined. For example, FrequentInlineSize or MaxInlineSize is configured.
Possible problems with using method inlining
Let’s explore the potential problems associated with using method inlining.
First of all, method inlining is not a panacea. It has its drawbacks. Inlining is essentially a space-for-time play, where the just-in-time compiler concatenates method calls at compile time to reduce the overhead of loading and unloading. However, there is more code after being inlined, and the amount of code added depends on the number of times the method is called and the size of the method itself.
So in extreme cases, inlining can even cause the CodeCache to overflow, so the CodeCache is a cache of hot code, where the compiled code of the compiler and the local method code are stored. This space is relatively limited; JDK 8 only has 240 MB by default. This space, if spilled, can even cause the JVM to abandon compilation runs and degenerate into interpreting execution patterns.
So that’s all you need to know for now. Tuning CodeCache is discussed in more detail in a later section. Finally, we summarize the JVM parameters associated with method inlining.
parameter | The default | instructions |
---|---|---|
-XX:+PrintInlinging | – | Need to print inline details, please parameters and – XX: + UnlockDiagnosticVMOptions cooperate to use |
-XX:+UnlockDiagnosticVMOptions | – | Prints information about JVM diagnostics |
-XX:MaxInlineSize=n | 35 | If the bytecode of a non-hot method exceeds this value, it cannot be inlined, in bytes |
-XX:FreqInlineSize=n | 325 | If the bytecode of the hotspot method exceeds this value, it cannot be inlined, in bytes |
-XX:InlineSmallCode=n | 1000 | The machine code generated by the target compilation cannot be inlined if the value is greater than this value, in bytes |
-XX:MaxInlineLevel=n | 9 | Maximum number of call frames for inline methods (maximum inline depth for nested calls) |
-XX:MaxTrivialSize=n | 6 | If the bytecode of the method is less than this value, the unit byte is inlined |
-XX:MinlnliningThreshould=n | 250 | If the target method is called less times than this value, it is not inlined |
-XX:LiveNodeCountInliningCutoff=n | 40000 | An upper limit on the maximum number of active nodes (IR nodes) during compilation, valid only for the C2 compiler |
-XX:InlineFrequencyCount=n | 100 | If the method’s call site is executed more times than this value, inlining is triggered |
-XX:MaxRecursiveInlineLevel=n | 1 | Recursive calls greater than this value are not inline |
-XX:InlineSynchronizedMethods | open | Whether to enable inline synchronization |
This should be a full inline parameter.
To do an experiment with these parameters, let’s print out the inline details of the method in our case.
public class InlineDemo1 { private static final Logger LOGGER = LoggerFactory.getLogger(InlineDemo1.class); public static void main(String[] args) { long cost=compute(); Logger. info(" execution cost {}",cost); } private static long compute(){ long start=System.currentTimeMillis(); int result=0; Random random=new Random(); for (int i=0; i<100000000; i++){ int a=random.nextInt(); int b=random.nextInt(); int c=random.nextInt(); int d=random.nextInt(); result=add1(a,b,c,d); } long end =System.currentTimeMillis(); return end-start; } private static int add1(int x1,int x2,int x3,int x4){ return add2(x1,x2)+add2(x3,x4); } private static int add2(int x1,int x2){ return x1+x2; }}Copy the code
Configure the following information in IDEA:
It is worth noting: – XX: + UnlockDiagnosticVMOptions parameters must be – XX: + PrintInlining parameters before.
After successful execution, the console outputs the result, let’s see if the method is inlined.
As a result of the run, our method is inlined and hot, and the number of bytes in the method body is printed. At present, add1 method requires 12 bytes, add2 method requires 4 bytes, and the execution time is 5095 ms.
Let’s take this method out of line again and compare the performance difference between methods that are inlined and not inlined. Set this parameter to uninline with -xx :FreqInlineSize=1. Remember what this parameter does, it specifies the byte value of the hot method’s bytecode, and if it exceeds this value, it is not inlined. Now our methods are all longer than 1 byte, so setting it to 1 will not be inlined.
After executing the program, it can be seen that when not inlined, the execution time is: 6847 ms.
It’s not hard to see how inlining can have an impact on performance when methods are called frequently. In a real project, however, I personally do not recommend arbitrarily configuring these JVM parameters. Although these parameters are very flexible and very rich. I personally recommend using the default values in normal cases, because the JVM is so smart now.
In general, we can ignore these details and leave optimization to the JVM. However, when a project has a performance bottleneck, you should be able to think of a tuning mechanism called method inlining and be able to tune it accordingly.