Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JVM Performance Magic Tricks


Published on

HotSpot, the JVM we all know and love, is the brain in which our Java and Scala juices flow. At its core lies the JIT (“Just-In-Time”) compiler, whose sole purpose is to make your code run fast. Here are some of the more interesting optimizations performed by it.

JVM Performance Magic Tricks

  1. 1. HotSpot● The brain in which our Java and Scala juicesflow.● Its execution speed and efficiency isnearing that of native compiled code.● At its core: The JIT compiler.
  2. 2. So... The JIT compiler?● Information is gathered at runtime.○ Which paths in the code are traveled often?○ Which methods are called the most, or, where arethe hot spots?
  3. 3. So... The JIT compiler?● Once enough information about a hotmethod is collected...○ The compiler kicks in.○ Compiles the bytecode into a lean and efficientnative version of itself.○ May re-compile later due to over-optimism.
  4. 4. Some standard optimizations● Simple method inlining.● Dead code removal.● Native math ops instead of library calls.● Invariant hoisting.
  5. 5. Some are extra awesome!
  6. 6. Divide and conquerHow many times have you used the followingpattern?StringBuilder sb = new StringBuilder("Ingredients: ");for (int i = 0; i < ingredients.length; i++) {if (i > 0) {sb.append(", ");}sb.append(ingredients[i]);}return sb.toString();
  7. 7. Divide and conquer...or perhaps this one?boolean nemoFound = false;for (int i = 0; i < fish.length; i++) {String curFish = fish[i];if (!nemoFound) {if (curFish.equals("Nemo")) {System.out.println("Nemo! There you are!");nemoFound = true;continue;}}if (nemoFound) {System.out.println("We already found Nemo!");} else {System.out.println("We still havent found Nemo :(");}}
  8. 8. Divide and conquer● Both loops do one thing for a while,● Then another thing from a certain point on.● The compiler can spot these patterns.○ Split the loops into cases.○ “Peel” several iterations.
  9. 9. Divide and conquer● The condition: if (i > 0)○ false once,○ true thereafter.○ Peel one iteration!StringBuilder sb = new StringBuilder("Ingredients: ");for (int i = 0; i < ingredients.length; i++) {if (i > 0) {sb.append(", ");}sb.append(ingredients[i]);}return sb.toString();
  10. 10. Divide and conquer...will compile as if it were written like so:StringBuilder sb = new StringBuilder("Ingredients: ");if (ingredients.length > 0) {sb.append(ingredients[0]);for (int i = 1; i < ingredients.length; i++) {sb.append(", ");sb.append(ingredients[i]);}}return sb.toString();First iterationAll other iterations
  11. 11. Living on the edge● Null checks are bread-and-butter.● Sometimes null is a valid value:○ Missing values○ Error indication● Sometimes we check just to be on the safeside.
  12. 12. Living on the edgeSome checks may be practically redundant.If your code behaves well, the assertion maynever fail.public static String l33tify(String phrase) {if (phrase == null) {throw new IllegalArgumentException("Null bad!");}return phrase.replace(e, 3);}
  13. 13. Living on the edge● Code runs many, many times.● The assertion never fails.● The JIT compiler is optimistic....assumes the check is unnecessary!
  14. 14. Living on the edgeThe compiler may drop the check altogether,and compile it as if it were written like so:public static String l33tify(String phrase) {if (phrase == null) {throw new IllegalArgumentException("Null bad!");}return phrase.replace(e, 3);}
  15. 15. Living on the edgeWait...What if that happy-path assumptioneventually proves to be wrong?
  16. 16. Living on the edge● The JVM is now executing native code.○ A null reference would not result in a fuzzyNullPointerException....but rather in a real, harsh memoryaccess violation.
  17. 17. Living on the edge● The JVM intercepts the SIGSEGV (and recovers)● Follows-up with a de-optimization....Method is recompiled, this time with thenull check in place.
  18. 18. Virtual insanityThe JIT compiler has dynamic runtime data onwhich it can rely when making decisions.
  19. 19. Virtual insanityMethod inlining:Step 1: Take invoked method.Step 2: Take invoker method.Step 3: Embed former in latter.
  20. 20. Virtual insanityMethod inlining:○ Useful when trying to avoid costly invocations.○ Tricky when dealing with dynamic dispatch.
  21. 21. Virtual insanitypublic class Main {public static void perform(Song s) {s.sing();}}public interface Song {public void sing();}
  22. 22. Virtual insanitypublic class GangnamStyle implements Song {@Overridepublic void sing() {println("Oppan gangnam style!");}}public class Baby implements Song {@Overridepublic void sing() {println("And I was like baby, baby, baby, oh");}}public class BohemianRhapsody implements Song {@Overridepublic void sing() {println("Thunderbolt and lightning, very very frightening me");}}
  23. 23. Virtual insanity● perform might run millions of times.● Each time, sing is invoked.This is a co$tly dynamic dispatch!
  24. 24. Virtual insanityInlining polymorphic calls is not so a static compiler.
  25. 25. Virtual insanityThe JIT compiler is dynamic.Take advantage of runtime information!
  26. 26. Virtual insanityThe JVM might decide, according to thestatistics it gathered, that 95% of theinvocations target an instance ofGangnamStyle.
  27. 27. Virtual insanityThe compiler can perform an optimisticoptimization:Eliminate the virtual calls to sing....or most of them anyway.
  28. 28. Virtual insanityOptimized compiled code will behave like so:public static void perform(Song s) {if (s fastnativeinstanceof GangnamStyle) {println("Oppan gangnam style!");} else {s.sing();}}
  29. 29. Can I help?● The JIT compiler is built to optimize:○ Straightforward, simple code.○ Common patterns.○ No nonsense.
  30. 30. Can I help?The best way to help your compiler is to nottry so hard to help it.Just write your code as you otherwise would!