Вграждане на метод в JVM

1. Въведение

В този урок ще разгледаме какъв метод е вграждането в Java Virtual Machine и как работи.

Също така ще видим как да получим и прочетем информацията, свързана с вграждането от JVM и какво можем да направим с тази информация, за да оптимизираме нашия код.

2. Какъв метод е вграждането?

По принцип вграждането е начин за оптимизиране на компилирания изходен код по време на изпълнение, като замества извикванията на най-често изпълняваните методи с неговите тела.

Въпреки че има включена компилация, тя не се изпълнява от традиционния компилатор на javac , а от самия JVM. За да бъдем по-точни, отговорността е на компилатора Just-In-Time (JIT) , който е част от JVM; javac произвежда само байт код и позволява на JIT да направи магията и да оптимизира изходния код.

Едно от най-важните последици от този подход е, че ако компилираме кода, използвайки стара Java, същото. class файлът ще бъде по-бърз при по-новите JVM. По този начин не е необходимо да прекомпилираме изходния код, а само да актуализираме Java.

3. Как JIT го прави?

По същество JIT компилаторът се опитва да вмъкне методите, които често извикваме, за да можем да избегнем режийните разходи за извикване на метод . При вземането на решение дали да се вгради метод или не, се вземат предвид две неща.

Първо, той използва броячи, за да следи колко пъти извикваме метода. Когато методът е извикан повече от определен брой пъти, той става „горещ“. Този праг е зададен на 10 000 по подразбиране, но можем да го конфигурираме чрез флага на JVM по време на стартиране на Java. Определено не искаме да вграждаме всичко, тъй като това би отнело много време и би създало огромен байт код.

Трябва да имаме предвид, че вграждането ще се осъществи само когато стигнем до стабилно състояние. Това означава, че ще трябва да повторим изпълнението няколко пъти, за да предоставим достатъчно информация за профилиране за JIT компилатора.

Освен това, ако сте „горещ“, не гарантира, че методът ще бъде вграден. Ако е твърде голям, JIT няма да го вгради. Допустимият размер е ограничен от флага -XX: FreqInlineSize = , който определя максималния брой инструкции за байт код, които да бъдат вградени за метод.

Независимо от това, силно се препоръчва да не променяте стойността по подразбиране на този флаг, освен ако не сме напълно сигурни, че знаем какво въздействие може да окаже. Стойността по подразбиране зависи от платформата - за 64-битов Linux това е 325.

JIT inlines на статиката , частни , или крайни методи като цяло . И докато публичните методи също са кандидати за вграждане, не всеки публичен метод задължително ще бъде вграден. JVM трябва да определи, че има само едно изпълнение на такъв метод . Всеки допълнителен подклас би предотвратил вграждането и производителността неизбежно ще намалее.

4. Намиране на горещи методи

Със сигурност не искаме да гадаем какво прави JIT. Следователно се нуждаем от някакъв начин, за да видим кои методи са вградени или не. Лесно можем да постигнем това и да регистрираме цялата тази информация в стандартния изход, като зададем някои допълнителни JVM флагове по време на стартиране:

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

Първият флаг ще се регистрира, когато се случи JIT компилация. Вторият флаг позволява допълнителни флагове, включително -XX: + PrintInlining , който ще отпечата какви методи се вмъкват и къде.

Това ще ни покаже вградените методи под формата на дърво. Листата са отбелязани и маркирани с една от следните опции:

  • вграден (горещ) - този метод е маркиран като горещ и е вграден
  • твърде голям - методът не е горещ, но също така генерираният му байт код е твърде голям, така че не е вграден
  • горещият метод е твърде голям - това е горещ метод, но не е вграден, тъй като байт кодът е твърде голям

Трябва да обърнем внимание на третата стойност и да се опитаме да оптимизираме методите с етикет „горещ метод е твърде голям“.

По принцип, ако не намерим горещ метод с много сложна условен израз, ние трябва да се опитаме да се разделят на съдържанието на if- отчета и да се увеличи детайлността, така че СЕР може да оптимизира кода. Същото се отнася и за ключа и със стопанска отчети веригата.

Можем да заключим, че вграждането на ръчен метод е нещо, което не е необходимо да правим, за да оптимизираме нашия код. JVM го прави по-ефективно и вероятно бихме направили кода дълъг и труден за следване.

4.1. Пример

Нека сега видим как можем да проверим това на практика. Първо ще създадем прост клас, който изчислява сумата от първите N последователни положителни числа:

public class ConsecutiveNumbersSum { private long totalSum; private int totalNumbers; public ConsecutiveNumbersSum(int totalNumbers) { this.totalNumbers = totalNumbers; } public long getTotalSum() { totalSum = 0; for (int i = 0; i < totalNumbers; i++) { totalSum += i; } return totalSum; } }

След това прост метод ще използва класа за извършване на изчислението:

private static long calculateSum(int n) { return new ConsecutiveNumbersSum(n).getTotalSum(); }

И накрая, ще извикаме метода различен брой пъти и ще видим какво ще се случи:

for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) { calculateSum(i); }

При първото изпълнение ще го стартираме 1000 пъти (по-малко от праговата стойност от 10 000, спомената по-горе). Ако търсим изхода за метода CalcuSum () , няма да го намерим. Това се очаква, тъй като не му се обадихме достатъчно пъти.

Ако сега променим броя на итерациите на 15 000 и отново потърсим изхода, ще видим:

664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) inline (hot)

Виждаме, че този път методът отговаря на условията за вграждане и JVM го е очертал.

Забележително е да споменем отново, че ако методът е твърде голям, JIT няма да го вгради, независимо от броя на итерациите. Можем да проверим това, като добавим друг флаг при стартиране на приложението:

-XX:FreqInlineSize=10

Както можем да видим в предишния изход, размерът на нашия метод е 12 байта. В -XX: FreqInlineSize знаме ще ограничи размера на допустимите метод за Вграждането до 10 байта. Следователно, вграждането не трябва да се извършва този път. И наистина, можем да потвърдим това, като погледнем отново резултата:

330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) hot method too big

Въпреки че сме променили стойността на флага тук за целите на илюстрацията, трябва да подчертаем препоръката да не се променя стойността по подразбиране на флага -XX: FreqInlineSize, освен ако не е абсолютно необходимо.

5. Заключение

В тази статия видяхме какъв метод за вграждане е в JVM и как го прави JIT. Описахме как можем да проверим дали нашите методи отговарят на условията за вграждане или не и предложихме как да използваме тази информация, опитвайки се да намалим размера на често наричани дълги методи, които са твърде големи, за да бъдат вградени.

Накрая илюстрирахме как можем да идентифицираме горещ метод на практика.

Всички кодови фрагменти, споменати в статията, могат да бъдат намерени в нашето хранилище GitHub.