Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Java Keeps Throttling Up!

976 vues

Publié le

Le slide deck de l'Université que nous avons donnée avec Rémi Forax à Devoxx France 2019.

Comme promis, Java sort sa version majeure tous les 6 mois. Le train passe et amène son lot de nouveautés. Parmi elles, certaines sont sorties : une nouvelle syntaxe pour les clauses switch et l'instruction de byte code CONSTANT_DYNAMIC. D'autres sont en chantier, plus ou moins avancé : une nouvelle façon d'écrire des méthodes de façon condensée, un instanceof 'intelligent', des constantes évaluées au moment où elles sont utilisées. Les projets progressent. Loom, et son nouveau modèle de programmation concurrente que l'ont peut tester avec Jetty. Amber, qui introduit les data types et des nouvelles syntaxes. Valhalla, dont les value types donnent leurs premiers résultats. S'il est difficile de prévoir une date de sortie pour ces nouveautés, on sait en revanche qu'une fois prêtes elles sortiront en moins de 6 mois. De tout ceci nous parlerons donc au futur et en public, avec des démonstrations de code, des slides, du code, de la joie et de la bonne humeur !

Publié dans : Formation
  • Identifiez-vous pour voir les commentaires

Java Keeps Throttling Up!

  1. 1. Java Keeps Throttling Up!
  2. 2. Rémi ForaxRémi Forax StarringStarring José PaumardJosé Paumard
  3. 3. var local type var inference 10
  4. 4. var local type var inference 10 11 var as lambda parameters raw string nestmatescondy inner classes private in VM
  5. 5. 11 var as lambda parameters nestmatescondy inner classes private in VM 12 expr switch type switch record const lambda
  6. 6. 11 var as lambda parameters nestmatescondy inner classes private in VM 12 expr switch type switch record const lambda 13+ sealed interface pattern matching value type raw string
  7. 7. Public Poll! You can get Kahoot! to participate in the poll, just before the coffee break
  8. 8. Les bons artistes copient, les grands artistes volent
  9. 9. Chapter 1 Graal
  10. 10. Anatomy of a Java Distribution Language Tools – compiler: javac, ecj – jshell, jlink, javadoc, javap, jdb, etc Runtime Environment – A class library: OpenJDK, Harmony (discontinuated) – A virtual Machine: Hotspot, J9 ● Bytecode compiler: c1 / c2, graal (java), falcon (llvm) ● Garbage collector: CMS / G1 / ZGC, shenandoah, c4
  11. 11. Graal A Java bytecode compiler written in Java JVM Compiler Interface (JEP 243) – Interface between the VM and the bytecode compiler – Hotspot and J9 support JVMCI (since Java 9) But Graal recompiles itself at the same time it compiles the application ?
  12. 12. Graal JIT + AOT Two configurations – Just in Time Compiler (like c1 and c2) – Ahead of Time Compiler (like any C compilers) Graal compiles itself ahead of time (since april 2019) – libgraal (shared library) – Clean separation of the memory resource
  13. 13. Trade off between JIT vs AOT JIT knows runtime informations – Exact CPU version – Application Profiling (delayed JIT) – Can not use slow optimizations AOT knows all the codes – Fast startup time, lower runtime overhead – No dynamic support (no dynamic classloading, no bytecode generation) – Can not use optimizations that generates too much assembly codes
  14. 14. What About the JIT Performances ? Let us take a well-known benchmark: The computation of the Mandelbrot set Easy!
  15. 15. Julia Sets Comes from the Julia set: For a given value of c, this is the set of z0 For which zn+1 = zn 2 + c is bound If |zn| becomes greater than 2 Then z0 does not belong to the Julia set
  16. 16. Mandelbrot Set Set of c for which z0 = 0 zn+1 = zn 2 + c Is bound
  17. 17. Computing the Mandelbrot Set Make each point of an image a complex number c Compute the following: z0 = 0 zn+1 = zn 2 + c If for some n, |zn| becomes greater than 2 Then c does not belong to the Mandelbrot set
  18. 18. Computing the Mandelbrot Set Implementation: take an image of size 64x64 For each pixel z If n reaches 1000 and |zn| stays lower than 2 Then the point belongs to the set So there are about 16M multiplications to be made All the multiplications are different
  19. 19. Computing the Mandelbrot Set Complex provides a useful abstraction for (var col = -32; col < +32; col++) { for (var row = -32; row < +32; row++) { var c = new Complex(col, row); var zn = new Complex(0.0, 0.0); var iteration = 0; while (zn.modulus() < 4 && iteration < max) { zn = zn.square().add(c); iteration++; } if (iteration < max) { image.setRGB(col, row, colors[iteration]); } else { image.setRGB(col, row, black); } } }
  20. 20. Computing the Mandelbrot Set Computing the Mandelbrot set C style for (var col = -32; col < +32; col++) { for (var row = -32; row < +32; row++) { var an = 0.0; var bn = 0.0; var iteration = 0; while (an*an + bn*bn < 4 && iteration < max) { var next_a = an*an - bn*bn + col; var next_b = 2*an*bn + row; an = next_a; bn = next_b; iteration++; } if (iteration < max) { image.setRGB(col, row, colors[iteration]); } else { image.setRGB(col, row, black); } } }
  21. 21. JIT Performance with JMH Time to render Mandelbrot 64x64 With jdk 11 (c1 + c2) score error primitive 1540 µs/op 22 µs/op reference 2816 µs/op 48 µs/op
  22. 22. JIT Performance with JMH Time to render Mandelbrot 64x64 libgraal (ce-rc15) -XX:+UseJVMCICompiler score error primitive 1544 µs/op 10 µs/op reference 1542 µs/op 7 µs/op
  23. 23. GraalVM: a Java 8 Distribution Language Tools – compiler: javac, ecj – jshell, jlink, javadoc, javap, jdb, native-image, etc Runtime Environment – A class library: OpenJDK + Truffle – A virtual Machine: Hotspot ● Bytecode compiler: c1 / c2, graal (java) ● Garbage collector: CMS / G1 / ZGC – A C backend optimizer: llvm
  24. 24. native-image Precompile a JVM-based application into one executable Use Graal AOT + SubstrateVM SubstrateVM – Low memory footprint VM – No dynamic linking support – Use low pause non generationnal GC Support Java, Scala, Kotlin, Clojure
  25. 25. Truffle Write an interpreter with some specialization machinery to help the partial evaluator (Graal), get a compiler for free Truffle is a Java library – runs on Hotspot or Substrate VM – ask Graal to aggressively optimize the code – Allows (dynamic) languages interoperability at runtime
  26. 26. In a Nutshell A Java 8 application can be executed on: 1) HotSpot with C1 / C2 as JIT compilers 2) HotSpot with Graal as JIT compiler 3) A Jlink generated JRE with HotSpot 4) A native image, AOT compiled with Graal, executed on SubstrateVM
  27. 27. What Do You Need to Compare ? How much time it takes to link the application ? The size of the application ? How fast does it execute ?
  28. 28. Link-time, run-time & storage scan rt.jar and compute stats link-time run-time storage jdk11 - 1.70 s 288 k (304 M) jdk8 + libgraal - 1.32 s 288 k (878 M) jdk11 + jlink 2.2 s 1.53 s 43 M native-image 22 s (43s) 0.79 s 10 M
  29. 29. OpenJDK Project Metropolis Rewrite Hotspot in Java Use libgraal as JIT to replace c1/c2 Rewrite the runtime in Java Interpreter in Java ? Rewrite the GCs in Java Need a JVMGCI ?
  30. 30. Chapter 2 Valhalla
  31. 31. C’s Struct on stack
  32. 32. Stack allocation in C C allows stack allocation typedef struct _complex { double re; double im; } complex; complex c; // allocate re and im on stack Abstraction with no allocation cost !
  33. 33. Aliasing and Stack If structs are passed as pointers, the compiler doesn’t know if it’s the same address is in memory or not void foo(complex* c1, complex* c2) { c1 -> re = 5; // does c2 -> re has been changed or not ? } In Java, you can not create an address on stack Because the VM re-arranges the stack frames at runtime
  34. 34. Value class A simple immutable class tagged as value public value class Complex { private final double re; private final double im; public Complex(double re, double im) { this.re = re; this.im = im; } public Complex add(Complex c) { return new Complex(re + c.re, im + c.im); } }
  35. 35. Value type Immediate value with no header – Immutable and final – No identity (no address in heap) – Non null – Can implement interfaces Motto: “Code like a class, works like an int”
  36. 36. Back to the Mandelbrot Set Complex provides a useful abstraction for (var col = -32; col < +32; col++) { for (var row = -32; row < +32; row++) { var c = new Complex(row, col); var zn = new Complex(0.0, 0.0); var iteration = 0; while (zn.modulus() < 4 && iteration < max) { zn = zn.square().add(c); iteration++; } if (iteration < max) { image.setRGB(col, row, colors[iteration]); } else { image.setRGB(col, row, black); } } }
  37. 37. Demo Mandelbrot
  38. 38. JMH How much time to render the Mandelbrot 64x64 with 1000 iterations? With java -XX:+EnableValhalla score error primitive 1540 µs/op 22 µs/op reference 2816 µs/op 48 µs/op value 1541 µs/op 12 µs/op
  39. 39. Optional/Option public value class Option<E> { private final E value; private Option(E value) { this.value = value; } public static <E> Option<E> empty() { return Option<E>.default; } public static <E> Option<E> of(E value) { Objects.requireNonNull(value); return new Option<>(value); } public static <E> Option<E> ofNullable(E value) { return (value == null)? empty(): of(value); } ... All fields are 0, null, 0.0, etc
  40. 40. Demo Optional
  41. 41. Benchmark with JMH private static final Map<String, Integer> MAP = Map.of("foo", 1, "bar", 2, "baz", 3); private static final List<String> LIST = List.of("a", "foo", "b", "bar", "c", "baz"); @Benchmark public int sum_null() { int sum = 0; for(var key: LIST) { var value = MAP.get(key); sum += (value != null)? value: 0; } return sum; } @Benchmark public int sum_optional() { int sum = 0; for(var key: LIST) { var value = Optional.ofNullable(MAP.get(key)); sum += value.orElse(0); } return sum; }
  42. 42. Benchmark with JMH public final class value Option<E> { private final E value; private Option(E e) { this.e = e; } public Option<E> of(E e) { Objects.requireNonNull(e); return new Option<>(e); } public static <E> Option<E> empty() { return Option<E>.default; } public static <E> Option<E> ofNullable(E value) { return (value == null)? empty(): of(value); } /* rest of the class */ } @Benchmark public int sum_option() { int sum = 0; for(var key: LIST) { var value = Option.ofNullable(MAP.get(key)); sum += value.orElse(0); } return sum; }
  43. 43. JMH Time to compute the sum With java -XX:+EnableValhalla score error sum_null 60.9 ns/op 0.9 ns/op sum_optional 71.6 ns/op 3.6 ns/op sum_option 61.3 ns/op 0.4 ns/op
  44. 44. Classes are not immutable Problem: a class is not truly immutable – You can see the field being initialized in the constructor Only initialize through static factory – Add 2 new opcodes: default/with – Let the compiler do the lifting But not binary compatible => bridges ??
  45. 45. Constructor are static factory public value class IntBox { private final int value; private IntBox(int value) { this.value = value; } public int intValue() { return value; } public static IntBox zero() { return IntBox.default; } public static IntBox valueOf(int value) { var box = IntBox.default; box = __WithField(box.value, value); return box; } } WithField allows to change one field
  46. 46. Flattenable Storing a value type in a field or an array – Less pointer chasing! Problems – with volatile field ● don’t flatten if volatile – When writing without synchronized or volatile ● value type tearing :(
  47. 47. Array flattening ? Array of value types are flattenable – Don’t flatten if too big ! header header header header header An array of java.lang.Integer An array of IntBox
  48. 48. Demo Flattening
  49. 49. Benchmark Sum of an array of value type private static final IntBox[] ARRAY = new IntBox[100_000]; static { range(0, ARRAY.length).forEach(i -> ARRAY[i] = IntBox.valueOf(i)); } @Benchmark public int sum_IntBox() { var sum = 0; for(var value : ARRAY) { sum += value.intValue(); } return sum; }
  50. 50. JMH Time to sum an array of 100 000 values With java -XX:+EnableValhalla score error primitive (int) 27.1 µs/op 0.2 µs/op reference (Integer) 60.7 µs/op 0.6 µs/op Value (IntBox) 27.0 µs/op 0.1 µs/op
  51. 51. Benchmark What if we shuffle the values ? private static final IntBox[] ARRAY = new IntBox[100_000]; static { range(0, ARRAY.length).forEach(i -> ARRAY[i] = IntBox.valueOf(i)); Collections.shuffle(Arrays.asList(ARRAY)); } @Benchmark public int sum_IntBox() { var sum = 0; for(var value : ARRAY) { sum += value.intValue(); } return sum; }
  52. 52. JMH Time to sum an array of 100 000 random values With java -XX:+EnableValhalla score error primitive (int) 27.1 µs/op 0.2 µs/op reference (Integer) 121.9 µs/op 5.1 µs/op Value (IntBox) 27.0 µs/op 0.1 µs/op
  53. 53. First Prototype Minimum Value Type – Reference Object can be unboxed to a value type – Value type are not subtype of Object ● Need to box first
  54. 54. Second Prototype L-World – Subtypes of Object doesn’t work exactly like an int :( Minimum Value Type – Reference Object can be unboxed to a value type – Value type are not subtype of Object ● Need to box first
  55. 55. L-world issues Operations that requires an object header ● synchronized throws IllegalStateMonitorException ● == – opt-in to component-wise comparison or – always false Arrays are covariant ● Array access are polymorphic :( – The JIT try to aggressively hoist the element check
  56. 56. L-world issues : Generics Erased so no flattening ● Reify generics over value types A type parameter can be null ● Need a nullable value type for old generics Complex?, IntBox?
  57. 57. L-world Issues : Concurrency Value types are subjects of tearing public value class SecurityToken { private final long id1, id2; public SecurityToken(long id1, long id2) { if (id1 != id2) throw new IllegalArgumentException(); ... } public void check() { if (id1 != id2) throw new IllegalStateException(); } public static void main(String[] args) { var tokens = new SecurityToken[1]; IntStream.range(0, 2).forEach(id -> { new Thread(() -> { for(;;) { tokens[0].check(); tokens[0] = new SecurityToken(id, id); } }).start(); }); } }
  58. 58. Votation inline class vs immediate class
  59. 59. Poll! 1. Inline class! 2. Immediate class! public immediate class Complex { … } public inline class Complex { … }
  60. 60. Coffee (or whatever) Break! Coffee (or whatever) Break!
  61. 61. Chapter 3 Loom
  62. 62. Erlang’s actors
  63. 63. Erlang Actor Scheduled by the Erlang VM (BEAM) counter(Sum) -> receive % wait for some message {increment} -> counter(Sum+1); % tail-call {value} -> io:format("~p~n", [Sum]); end. pid ! {increment}; % send a message to pid
  64. 64. Thread are heavyweight ! A Thread – Share the same address space – Lightweight Lock ● no context switch but – Thread stack is pre-reserved (=> OOM if a lot of threads) – Thread scheduling is done by the OS !
  65. 65. Concurrency models continuation model Memory OS VM T1 T2 T3 lock thread model Memory OS VM T1 T2 lock C1 C2 C3
  66. 66. Continuation Wrap a Runnable Continuation.run() execute the runnable – Can stop itself with a yield ! – A call to run() will restart the runnable just after the yield No language support needed
  67. 67. A simple example Create a continuation and run it var scope = new ContinuationScope("example"); var continuation = new Continuation(scope, () -> { System.out.println("hello Devoxx France"); }); System.out.println("start continuation"); continuation.run();
  68. 68. A simple example Create a continuation and call run(), yield(), run() var scope = new ContinuationScope("example"); var continuation = new Continuation(scope, () -> { System.out.println("hello Devoxx France"); Continuation.yield(scope); System.out.println("i’m back !"); }); System.out.println("start continuation"); continuation.run(); System.out.println("restart continuation"); continuation.run();
  69. 69. How does it work ? Yield copy the all stack frames on heap ! main run f1 f2 heap run f1 f2
  70. 70. How does it work ? Then run() move some stack frames back main f2 heap stack bang! The VM tries to guess how many stack frames should be moved A read of the stack bang trigger the copy of the remaining frames
  71. 71. Delimited Continuation vs Thread Scheduling explicit (yield, run) – No OS context switch No heap reservation – Only store what is actually needed !
  72. 72. Erlang Actor Scheduled by the Erlang VM (BEAM) counter(Sum) -> receive % wait for some message {increment} -> counter(Sum+1); % tail-call {value} -> io:format("~p~n", [Sum]); end. pid ! {increment}; % send a message to pid
  73. 73. Java Actor ? Scheduled by the JVM/JDK var actor = new Actor(new Runnable() { int sum; public void run() { while (true) { receive(message -> { // wait for some message switch((String)message { case "increment" -> sum++; // side effect case "value" -> { System.out.println(sum); exit(); } }}); } }}); actor.send("increment"); // send a message to the actor
  74. 74. Demo Actor
  75. 75. Demo TCP Proxy
  76. 76. Fiber Wrap a Runnable Scheduled using an ExecutorService – A fiber is scheduled on a thread Blocking calls (read, write, sleep) – Freeze the fiber, schedule another one – Re-scheduled ● When a read/write/locks/etc will not block ● Maybe scheduled on another thread
  77. 77. Fiber vs Continuation A fiber delegates the suspension (yield) to the continuation – All blocking calls do internally a yield – The JDK code calls run() on the continuation when it can be rescheduled All the JDK needs to be patched to be “fiber aware”
  78. 78. Fiber/Continuation limitation Continuation/Fiber – Can not yield if there is a native frame on the stack Fiber – Yielding inside a synchronized block pins the fiber to that thread ● The same thread as to be used when re-scheduling ● This limitation may be removed in the future
  79. 79. Thread related API ? Some Thread related APIs need to be reworked Thread.currentThread() – Need to lazily create a fake thread per fiber :( ThreadLocal/InheritableThreadLocal – Redirect thread local to fiber locals Will consume a lot of memory
  80. 80. Roadmap Tailcall support ? – Not implemented yet Performance ? – Still a prototype ! No version of Java targeted, yet !
  81. 81. Chapter 4 Amber
  82. 82. Groovy’s smart-cast
  83. 83. Precise type inference Groovy (with @CompileStatic) change the type of a variable after an instanceof Object x = ... if (x instanceof String) { print(x.length()); // x is a String here ! } // x is not a String here !
  84. 84. Instanceof in Java Extends the intanceof syntax to define a new variable Object x = ... if (x instanceof String s) { System.out.println(s.length()); } Avoid to introduce an intersection type if (x instanceof I || x instanceof J) { … }
  85. 85. Declaration/Definition The rules for declaration/definition are a little surprising class Point { private final int x; private final int y; … @Override public boolean equals(Object o) { if (!(o instanceof Point p) { return false; } return x == p.x && y == p.y; } ... } declaration definition
  86. 86. Declaration/Definition Other examples if (x instanceof String s && s.length() == 3) { … if (x instanceof String s || foo()) { /* no s here */ } (x instanceof String s)? s.length(): /* no s here */ while(x instanceof String s) { … s.length() }
  87. 87. Enhanced instanceof Not yet scheduled for a peculiar version of Java
  88. 88. Kotlin’s Data Class
  89. 89. Data storage class Sometimes a class is just a storage for data In Java, you get encapsulation by default, So you need to declare fields, write boring constructors, boring getters, boring equals, hashCode and toString
  90. 90. While in Kotlin you have data class data class User(val name: String, val age: Int) the compiler generates constructor, getters/setters, equals(), hashCode() and toString()
  91. 91. Known problems Data class in Kotlin Can be mutable So storing data class is a HashSet/HashMap is a mess You can still declare fields There are not used in equals/hashCode/toString ?? The boilerplate code is still there on disk
  92. 92. Record in Java record User(String name, int age) A record (a data carrier) is non mutable generates overridable ● Constructor, accessors: String name(), int age() ● equals/hashCode/toString – Implementation is provided by invokedynamic has no supplementary fields
  93. 93. An example The primary constructor can specify pre-conditions public record User(String name, int age) { public User { Objects.requireNonNull(name); } public boolean isOld() { return age > 14; } }
  94. 94. Record vs other features A record can be combined with a value type value record User(String name, int age) the keyword “value” acts as a modifier Records will have a special support for pattern matching (using extractors)
  95. 95. Haskell’s Pattern Matching
  96. 96. Abstract data type in Haskell A function is defined as a list of pattern matchs data Expr = Value Int | Add Expr Expr eval :: Expr -> Int eval (Value v) = v eval (Add l r) = eval(l) + eval(r)
  97. 97. Rough translation in Java New features: sealed interface + switch expression sealed interface Expr { record Value(int v) implements Expr; record Add(Expr l, Expr r) implements Expr; } int eval(Expr expr) { return switch(expr) { case Value(var v) -> v; case Add(var l, var r) -> eval(l) + eval(r); }; }
  98. 98. Demo !
  99. 99. What’s wrong with switch ? String[] tokens = ... Vehicle vehicle; switch(tokens[0]) { case "car": case "sedan": var color = tokens[1]; vehicle = new Car(color); break; case "bus": vehicle = new Bus("yellow"); break; default: throw new … }
  100. 100. What’s wrong with switch ? String[] tokens = ... Vehicle vehicle; switch(tokens[0]) { case "car": case "sedan": var color = tokens[1]; vehicle = new Car(color); break; case "bus": vehicle = new Bus("yellow"); break; default: throw new … } fallthrough weird scope initialization hidden
  101. 101. Fix the switch Separate the cases with a comma + arrow syntax String[] tokens = ... Vehicle vehicle; switch(tokens[0]) { case "car", "sedan" -> { var color = tokens[1]; vehicle = new Car(color); } case "bus" -> { vehicle = new Bus("yellow"); } default -> throw new … }
  102. 102. Add a switch expression A switch that can “return” a value var tokens = ... var vehicle = switch(tokens[0]) { case "car", "sedan" -> { var color = tokens[1]; ... } case "bus" -> new Bus("yellow"); default -> throw new … };
  103. 103. Exhaustive Switch The switch expression requires a “default” var tokens = ... var vehicle = switch(tokens[0]) { case "car", "sedan" -> { var color = tokens[1]; ... } case "bus" -> new Bus("yellow"); default -> throw new … };
  104. 104. Exhaustive Switch (again) For enums, default is not required if exhaustive enum Kind { CAR, SEDAN, BUS } var tokens = ... var vehicle = switch(kind) { case CAR, SEDAN -> { var color = tokens[1]; ... } case BUS -> new Bus("yellow"); };
  105. 105. Return a value from a block And not return from the method var tokens = ... var vehicle = switch(tokens[0]) { case "car", "sedan" -> { var color = tokens[1]; ??? new Car(color); } case "bus" -> new Bus("yellow"); default -> throw new … };
  106. 106. Return a value from a block And not return from the method var tokens = ... var vehicle = switch(tokens[0]) { case "car", "sedan" -> { var color = tokens[1]; break new Car(color); } case "bus" -> new Bus("yellow"); default -> throw new … };
  107. 107. Hyphen keyword Using ‘-’ to extend the number of keywords ! var tokens = ... var vehicle = switch(tokens[0]) { case "car", "sedan" -> { var color = tokens[1]; break-with new Car(color); } case "bus" -> new Bus("yellow"); default -> throw new … };
  108. 108. Switch enhancement Retrofit old switch – Arrow syntax and comma separated case values work for both switch Introduce switch expression – exhaustive – default not needed for enums
  109. 109. Switch enhancement Already in Java 12 – Under the flag --enable-preview Not a preview feature in Java 13 – Uses break-with instead of break
  110. 110. LISP’s macro
  111. 111. String concatenation A simple benchmark class PerfTest { static int value = 4; @Benchmark public String test_concat() { return "err: " + value + " !"; } @Benchmark public String test_builder() { return new StringBuilder().append("err: ") .append(value).append(" !").toString(); } @Benchmark public String test_format() { return String.format("err: %d !", value); } }
  112. 112. JMH With Java 11 String.format() is super slow ! score error test_concat 0.013 µs/op 0.001 µs/op test_builder 0.014 µs/op 0.001 µs/op test_format 0.502 µs/op 0.034 µs/op
  113. 113. Generated bytecodes The format parsing is super slow + boxing/varargs public static java.lang.String format(int); 0: ldc #10 // String err: %d ! 2: iconst_1 3: anewarray #11 // class java/lang/Object 6: dup 7: iconst_0 8: iload_0 9: invokestatic #12 // Integer.valueOf:(I)LInteger; 12: aastore 13: invokestatic #13 // String.format:(LString;[LObject;)LString; 16: areturn
  114. 114. JMH With amber-compiler intrinsics it’s not slow anymore ! score error test_concat 0.014 µs/op 0.001 µs/op test_builder 0.014 µs/op 0.001 µs/op test_format 0.014 µs/op 0.001 µs/op
  115. 115. Amber / intrinsics The compiler uses invokedynamic instead public static java.lang.String format(int); 0: iload_0 1: invokedynamic #10, 0 // #1:format:(I)LString; 6: areturn bootstraps 1: #35 invokeStatic IntrinsicFactory.staticStringFormatBootstrap (LLookup;LString;LmethodType;LString;)LCallSite; Method arguments: #36 err: %d !
  116. 116. Limitations Only works if – the format is a constant! – the format has no dependency on java.util.Locale! (double formatting) In the work – should work for JIT constant not only compiler constant – Consider the Locale as a constant + deoptimisation
  117. 117. Scala’s Lazy constant
  118. 118. Example in Java Java class initialization is lazy by default => so not issue here! But static block initialization is the devil Usually trigger other class initializations => applications are slooow to boot
  119. 119. Use Scala’s lazy keyword class Utils { public static final lazy Path HOME = Path.of(System.getenv("HOME")); } Problem: Scala uses the Double-checked Locking pattern
  120. 120. The Double-checked Pattern The correct implementation disables further useful optimizations :( class Utils { private static volatile Path HOME; public static Path getHome() { var home = HOME; // volatile read if (home == null) { synchronized(Utils.class) { home = HOME; if (home == null) { return HOME = Path.of(System.getenv("HOME")); } } } return home; } }
  121. 121. Constant Pool A classfile has a dictionnary of all constants (constant pool), each constant is transformed once to a runtime Java Object #30: ... #31: Utf8: "hello riviera dev" #32: String: #31 #33: ... class Utils { public static void main(String[] args) { //System.out.println("hello riviera dev"); ldc #32 ... } }
  122. 122. Constant Dynamic ConstandDynamic (Java 11) is a constant that can be computed dynamically #28: Utf8: "Ljava/nio/file/Path;" #29: Utf8: "HOME" #30: NameAndType: #29:#28 // HOME Ljava/nio/file/Path; #31: MethodHandle 6:#… // Utils.lazy_HOME_init ()Ljava/nio/file/Path; #32: ConstantDynamic: 00:#30 BootstrapMethods: 00: #31 class Utils { private static Path lazy_HOME_init() { return Path.of(System.getenv("HOME")); } public static void main(String[] args) { //System.out.println(??); ldc #32 ... } }
  123. 123. Demo !
  124. 124. Compilation Strategies A lazy static field Loaded from the same class ● Use ldc Loaded from another class ● Use getstatic ?
  125. 125. getstatic Opcode getstatic need to be changed to recognize attribute DynamicValue #30: NameAndType: #29:#28 // HOME Ljava/nio/file/Path; #31: MethodHandle 6:#… // Utils.lazy_HOME_init ()Ljava/nio/file/Path; #32: ConstantDynamic: 00:#30 #33: Field …:#30 // Utils.HOME Ljava/nio/file/Path; BootstrapMethods: 00: #31 class Utils { field: #30 DynamicValue: #32 private static Path lazy_HOME_init() { return Path.of(System.getenv("HOME")); } public static void main(String[] args) { //System.out.println(??); getstatic #33 ... } }
  126. 126. Lazy static : Java vs Scala Scala (double-checked pattern) – Guarantee singleton – Perf issue: Volatile read + nullcheck Java (getstatic + constant dynamic) – Singleton after creation ● need to be idempotent – Plain static final read ● constant for the VM
  127. 127. Lazy static final in Java Scheduled to be introduced in Java 14 “lazy” can not be used on final instance field
  128. 128. C#’s raw string
  129. 129. Example in Java Embedded code is unreadable var html = "<html>n" + " <body style="width: 100vw">n" + " <p>Hello World.</p>n" + " </body>n" + " <script>console.log("loadedn")</script>n" + "</html>";
  130. 130. Example in Java Embedded code has a lot of boilerplate var html = "<html>n" + " <body style="width: 100vw">n" + " <p>Hello World.</p>n" + " </body>n" + " <script>console.log("loadedn")</script>n" + "</html>";
  131. 131. Borrow C# raw string var html = @"<html> <body style=""width: 100vw""> <p>Hello World.</p> </body> <script>console.log(""loadedn"")</script> </html>"; but with a different delimiter – C# needs to de-specialized quotes – we can not embed C# easily in Java
  132. 132. Proposed Solution – Fat String Use """ to start and end a fat strings var html = """<html> <body style="width: 100vw"> <p>Hello World.</p> </body> <script>console.log("loadedn")</script> </html>""";
  133. 133. Examples With pattern matching var pattern = Pattern.compile(""""""); A multi-lines string var s = """this is a long multi-lines of text"""; Embedded code var s = """< a href="javascript: alert('hello')">""";
  134. 134. Fat/Raw String in Java Was initially scheduled for Java 11 Was a real raw String (no escape of u….) Using a variable number of backticks (`) But early adopter reactions were “meeh” Still in discussion Raw ? Auto-alignment ?
  135. 135. That’s it!
  136. 136. Questions??

×