In-depth understanding of Java Virtual Machines (14) How to properly leverage JVM methods inline

Source: Internet
Author: User
Tags stack trace

In IntelliJ idea, ctrl+alt+m is used to split the method. Select a piece of code, knock down this combination, very easy. Eclipse also uses a similar shortcut key, using Alt+shift+m. I hate the long way, and I think it's too long to mention the following method:

public void Processonendofday (contract c) {if (Dateutils.adddays (c.getcreated (), 7). Before (new Date ())) { Priorityhandling (c, Outdated_fee); notifyoutdated (c); Log.info ("outdated: {}", c);} else {if (sendnotifications) {notifypending (c);} Log.debug ("Pending {}", c);}}

First, it has a condition to infer that readability is very poor. No matter how it is implemented, what it does is the key. Let's split it up first:

public void Processonendofday (contract c) {if (Isoutdate (c)) {priorityhandling (c, Outdated_fee); notifyoutdated (c); Log.info ("outdated: {}", c);} else {if (sendnotifications) {notifypending (c);} Log.debug ("Pending {}", c);}} Private Boolean isoutdate (contract c) {return dateutils.adddays (c.getcreated (), 7). Before (new Date ());}

Obviously, this approach should not be put here:

public void Processonendofday (contract c) {if (C.isoutdate ()) {priorityhandling (c, Outdated_fee); notifyoutdated (c); Log.info ("outdated: {}", c);} else {if (sendnotifications) {notifypending (c);} Log.debug ("Pending {}", c);}}

Notice what's different? My IDE changed the Isoutdated method to an example of contract, which is just decent. It's just that I'm still upset. This method of doing is too miscellaneous. A branch handles business-related logic priorityhandling, as well as sending system notifications and logging. There is also a branch that makes system notifications based on inferred conditions and logs at the same time. We first split the processing of overdue contracts into a separate method.

public void Processonendofday (contract c) {if (C.isoutdate ()) {handleoutdated (c);} else {if (sendnotifications) { Notifypending (c);} Log.debug ("Pending {}", c);}} private void handleoutdated (contract c) {priorityhandling (c, Outdated_fee); notifyoutdated (c); Log.info ("outdated: {}" , c);}

Some people would think that's good enough, but I think the two branches are not exactly the most glaring. The Handleoutdated method level is higher, while the Else branch is more detail-biased. The software should be legible, so don't mix the code between the different tiers. This will make me more comfortable:

public void Processonendofday (contract c) {if (C.isoutdate ()) {handleoutdated (c);} else {stillpending (c);}} private void stillpending (contract c) {if (sendnotifications) {notifypending (c);} Log.debug ("Pending {}", c);} private void handleoutdated (contract c) {priorityhandling (c, Outdated_fee); notifyoutdated (c); Log.info ("outdated: {}" , c);}

This example looks a little bit loaded, but in fact I want to prove that there is one more thing. Although it is not common today, some developers are afraid to split the method and worry about the effect of execution efficiency. They didn't know that the JVM was actually a great software (it actually dumped a few streets in the Java language), and it built a lot of amazing execution-time optimizations. First of allShort method is more beneficial to JVM judgment。 The process is more pronounced,a shorter scope, and the side effects are more pronounced. The assumption is that a long method JVM may be kneeling directly. A second reason is more important:

Method inline

Assuming that the JVM detects that some small methods are running frequently, it replaces the call of the method with the method body itself. Let's say the following:

private int add4 (int x1, int x2, int x3, int x4) {return add2 (x1, x2) + ADD2 (x3, x4);} private int add2 (int x1, int x2) {return x1 + x2;}

It is certain that after a period of execution the JVM will remove the Add2 method and translate your code into:

private int add4 (int x1, int x2, int x3, int x4) {return x1 + x2 + x3 + x4;}

Note that this is about the JVM, not the compiler. Javac are more conservative when generating bytecode, and these jobs are thrown at the JVM. It turns out that this design decision is sensible:

The JVM is more aware of the target environment, CPU, memory, and architecture of the implementation, and it can be more aggressively optimized. The JVM can discover the characteristics of your code execution, for example, which method is executed frequently, which virtual method has only one implementation, and so on. The. Class compiled by the old compiler can get faster execution speed on the JVM of the new version number. Update the JVM and compile the source code again, you must be more inclined to the latter.

We'll do the test if we do this. I wrote a small program that has the worst realization of the principle of division. The add128 method requires 128 parameters and calls two ADD64 methods-two halves at a time. ADD64 is similar, just that it is called two times add32. You guessed it, and finally it was done by the Add2 method. Some numbers I have omitted, lest the light blinded your eyes:

public class Concreteadder {public int add128 (int x1, int x2, int x3, int x4, ..., int x127, int x128) {RE  Turn add64 (x1, x2, x3, x4, ..., x63, x64) + ADD64 (x65, x66, x67, x68, ..., x127, x128); } private int add64 (int x1, int x2, int x3, int x4, ..., int x63, int x64) {return add32 (x1, x2, X3, X4,...) .  More ..., x31, x32) + add32 (x33, x34, x35, x36, ..., x63, x64); } private int add32 (int x1, int x2, int x3, int x4, ..., int x31, int x32) {return add16 (x1, x2, X3, X4,...) .  More ..., x15, x16) + add16 (x17, x18, x19, x20, ..., x31, x32);  } private int Add16 (int x1, int x2, int x3, int x4, ..., int x15, int x16) {return Add8 (x1, x2, X3, X4, X5,  X6, X7, x8) + ADD8 (x9, X10, X11, X12, X13, x14, x15, x16); } private int Add8 (int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8) {return add4 (x1, x2, x3, x4) + ad  D4 (X5, X6, X7, x8); } private int add4 (int x1, int x2, int x3, int x4) {return add2 (x1, x2) + ADD2 (x3, x4);  } private int add2 (int x1, int x2) {return x1 + x2;  }}

It is not difficult to see that the call to the add128 method resulted in a total of 127 method invocations. Too much. As a reference, here is a simple and straightforward implementation of the version number:

public class Inlineadder {public     int add128n (int x1, int x2, int x3, int x4, ..., int x127, int x128) {        Return x1 + x2 + x3 + x4 + ... more ... + x127 + x128;    } }

Finally, another implementation version number that uses the abstract class and inheritance. The cost of 127 virtual method calls is large. These methods need to be distributed dynamically and therefore require higher, so they cannot be inline.

Public abstract class Adder {public   abstract int add128 (int x1, int x2, int x3, int x4, ... more ..., int x127, int x );   public abstract int Add64 (int x1, int x2, int x3, int x4, ..., int x63, int x64);   public abstract int add32 (int x1, int x2, int x3, int x4, ..., int x31, int x32);   public abstract int Add16 (int x1, int x2, int x3, int x4, ..., int x15, int x16);   public abstract int Add8 (int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8);   public abstract int Add4 (int x1, int x2, int x3, int x4);   

Another implementation:

public class Virtualadder extends Adder {@Override public int add128 (int x1, int x2, int x3, int x4, ... more ..., int x128) {return add64 (x1, x2, x3, x4, ..., x63, x64) + ADD64 (x65, x66, x67, x68, ... more ..., x127, x1  28); } @Override public int add64 (int x1, int x2, int x3, int x4, ..., int x63, int x64) {return add32 (x1, x2,  X3, x4, ... more ..., x31, x32) + add32 (x33, x34, x35, x36, ..., x63, x64); } @Override public int add32 (int x1, int x2, int x3, int x4, ..., int x32) {return add16 (x1, x2, X3, X4,. ..  More ..., x15, x16) + add16 (x17, x18, x19, x20, ..., x31, x32); } @Override public int add16 (int x1, int x2, int x3, int x4, ..., int x16) {return Add8 (x1, x2, X3, X4, X5  , X6, X7, x8) + ADD8 (x9, X10, X11, X12, X13, x14, x15, x16); } @Override public int add8 (int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8) {return add4 (x1, x2, x3 , x4) + add4 (X5, X6, X7, x8);  } @Override public int add4 (int x1, int x2, int x3, int x4) {return add2 (x1, x2) + ADD2 (x3, x4);  } @Override public int add2 (int x1, int x2) {return x1 + x2;  }}

Inspired by some enthusiastic readers of my article about the @cacheable load, I wrote a simple benchmark to compare the load of these two over-split concreteadder and Virtualadder. The results were surprising and somewhat confusing. I did a test on two machines (red and blue), the same program differs from the second machine CPU with a number of other and 64-bit cores:


Detailed environmental information:


On a machine that looks slow, the JVM prefers to do method inline. Not only is the version number of a simple private method call, but the version number of the virtual method is the same. Why is that? Since the JVM discovers that Adder has only one subclass, that means that each abstract method has only one version number. Assuming you load a subclass (or a lot of other) when you execute it, you'll see that performance is going to fall straight down, because it's impotent and then inline. First of all, judging from the test,

The calls to these methods are not very low overhead, there is no overhead at all!

Method calls (and documents added for readability) exist only in your source code and in the compiled bytecode, and they are completely erased when executed (inline).

I don't quite understand the second result. It appears that high performance machine B performs a single method call faster, while the other two is slower. Perhaps it tends to delay the inline? The result is somewhat different, but the gap is not so big. Just like the optimization stack trace information generation-assuming you are manually inline in order to optimize code performance, the bigger the way, the more complex it is, the more you are wrong.

Ps:64bit the slow execution of the machine may be due to the long length of the method required by the JVM inline.

The original article is from:

Http://www.javacodegeeks.com/2013/02/how-aggressive-is-method-inlining-in-jvm.html
Http://it.deepinmind.com/java/2014/03/01/JVM the method inline. html

In-depth understanding of Java Virtual Machines (14) How to properly leverage JVM methods inline

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.