Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Less waste, more joy, and a lot more green: How...

Less waste, more joy, and a lot more green: How Quarkus makes Java better

Quarkus makes both people and hardware more efficient. That’s cool, but how does it work? Usually, we expect to trade-off developer experience against runtime efficiency. In this session, Holly will dive into some of the technical underpinnings of Quarkus’s efficiency. She’ll give advice for those using or considering Quarkus - should you be doing reactive programming? Do native binaries run fastest? In this under-the-hood session, she’ll also share general principles and transferrable discoveries from the engineering team, such as knowing what you’re optimising for, avoiding cache pollution, and how to be static-but-dynamic.

Holly Cummins

November 05, 2024
Tweet

More Decks by Holly Cummins

Other Decks in Programming

Transcript

  1. Holly Cummins Øredev November 6, 2024 less waste, more joy,

    and a lot more green how quarkus makes java better
  2. quarkus applications start fast quarkus + graalvm 0.014 Seconds quarkus

    + open jdk 0.75 Seconds traditional cloud-native stack 4.3 Seconds https://quarkus.io/blog/runtime-performance/ rest application
  3. quarkus + GraalVM 13 MB quarkus + OpenJDK 74 MB

    traditional Cloud-Native Stack 140 MB rest application https://quarkus.io/blog/runtime-performance/ quarkus improves memory utilization
  4. @holly_cummins machine quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus

    quarkus quarkus quarkus quarkus quarkus quarkus container orchestration machine traditional cloud-native java stack traditional cloud-native java stack traditional cloud-native java stack traditional cloud-native java stack quarkus applications have high deployment density. quarkus native, but quarkus on jvm is also way smaller than traditional java
  5. @holly_cummins traditional cloud-native java stack traditional cloud-native java stack traditional

    cloud-native java stack traditional cloud-native java stack node.js node.js node.js node.js node.js node.js node.js go go machine go go go go go go go go go go go go go go go go go go go quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus quarkus … and not just when comparing to other java frameworks container orchestration machine machine machine https:/ /developers.redhat.com/blog/2017/03/14/java-inside-docker/
  6. let’s talk about throughput. quarkus native 3212 req/s https://www.redhat.com/en/resources/mi-quarkus-lab-validation-idc-analyst-paper a

    trade-off of throughput against footprint 48 concurrent connections traditional cloud native stack 3555 req/s
  7. but … traditional cloud native stack 3555 req/s quarkus native

    3212 req/s https://www.redhat.com/en/resources/mi-quarkus-lab-validation-idc-analyst-paper a trade-off of throughput against footprint 48 concurrent connections
  8. but … traditional cloud native stack 3555 req/s quarkus on

    jvm 6389 req/s quarkus native 3212 req/s https://www.redhat.com/en/resources/mi-quarkus-lab-validation-idc-analyst-paper a trade-off of throughput against footprint 48 concurrent connections
  9. but … traditional cloud native stack 3555 req/s quarkus on

    jvm 6389 req/s quarkus native 3212 req/s https://www.redhat.com/en/resources/mi-quarkus-lab-validation-idc-analyst-paper no trade-off, just better :) a trade-off of throughput against footprint 48 concurrent connections
  10. @holly_cummins if (a instanceof Thing) – Quarkus core – Netty

    – Hibernate ORM – Hibernate Reactive – Vert.x – Smallrye Mutiny – Smallrye Common – Vert.x Web – Infinispan – Camel – Drools – Optaplanner – Java Class Library problematic pattern: affects : !!
  11. @holly_cummins if (a instanceof Thing) – Quarkus core – Netty

    – Hibernate ORM – Hibernate Reactive – Vert.x – Smallrye Mutiny – Smallrye Common – Vert.x Web – Infinispan – Camel – Drools – Optaplanner – Java Class Library problematic pattern: affects : !! – Java Class Library!
  12. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } slow on many-core systems
  13. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } // Note that we intentionally use "instanceof" to test interfaces as the last resort in order to mitigate the "type pollution” // See https://github.com/RedHatPerf/type-pollution-agent for more information if (it instanceof AbstractCollection) { return ((AbstractCollection<?>) it).size(); } else if (it instanceof AbstractMap) { return ((AbstractMap<?, ?>) it).size(); } else if (it instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } return 10; } slow on many-core systems 30% more throughput
  14. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } // Note that we intentionally use "instanceof" to test interfaces as the last resort in order to mitigate the "type pollution” // See https://github.com/RedHatPerf/type-pollution-agent for more information if (it instanceof AbstractCollection) { return ((AbstractCollection<?>) it).size(); } else if (it instanceof AbstractMap) { return ((AbstractMap<?, ?>) it).size(); } else if (it instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } return 10; } but …
  15. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } // Note that we intentionally use "instanceof" to test interfaces as the last resort in order to mitigate the "type pollution” // See https://github.com/RedHatPerf/type-pollution-agent for more information if (it instanceof AbstractCollection) { return ((AbstractCollection<?>) it).size(); } else if (it instanceof AbstractMap) { return ((AbstractMap<?, ?>) it).size(); } else if (it instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } return 10; } but …
  16. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } // Note that we intentionally use "instanceof" to test interfaces as the last resort in order to mitigate the "type pollution” // See https://github.com/RedHatPerf/type-pollution-agent for more information if (it instanceof AbstractCollection) { return ((AbstractCollection<?>) it).size(); } else if (it instanceof AbstractMap) { return ((AbstractMap<?, ?>) it).size(); } else if (it instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } return 10; } not a fan of the fix but …
  17. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } // Note that we intentionally use "instanceof" to test interfaces as the last resort in order to mitigate the "type pollution” // See https://github.com/RedHatPerf/type-pollution-agent for more information if (it instanceof AbstractCollection) { return ((AbstractCollection<?>) it).size(); } else if (it instanceof AbstractMap) { return ((AbstractMap<?, ?>) it).size(); } else if (it instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } return 10; } not a fan of the fix non-idiomatic but …
  18. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } // Note that we intentionally use "instanceof" to test interfaces as the last resort in order to mitigate the "type pollution” // See https://github.com/RedHatPerf/type-pollution-agent for more information if (it instanceof AbstractCollection) { return ((AbstractCollection<?>) it).size(); } else if (it instanceof AbstractMap) { return ((AbstractMap<?, ?>) it).size(); } else if (it instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } return 10; } not a fan of the fix non-idiomatic difficult to maintain but …
  19. @holly-cummins.bsky.social #RedHat private static int extractSize(Object it) { if (it

    instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } else if (it.getClass().isArray()) { return Array.getLength(it); } else if (it instanceof Integer) { return ((Integer) it); } return 10; } // Note that we intentionally use "instanceof" to test interfaces as the last resort in order to mitigate the "type pollution” // See https://github.com/RedHatPerf/type-pollution-agent for more information if (it instanceof AbstractCollection) { return ((AbstractCollection<?>) it).size(); } else if (it instanceof AbstractMap) { return ((AbstractMap<?, ?>) it).size(); } else if (it instanceof Collection) { return ((Collection<?>) it).size(); } else if (it instanceof Map) { return ((Map<?, ?>) it).size(); } return 10; } not a fan of the fix non-idiomatic difficult to maintain machine efficiency my team’s efficiency but …
  20. @holly-cummins.bsky.social #RedHat string comparison: who says you have to read

    strings left-to-right? QUARKUS_REPEATED_PREFIX_FOO QUARKUS_REPEATED_PREFIX_BAR challenge assumptions
  21. @holly-cummins.bsky.social #RedHat string comparison: who says you have to read

    strings left-to-right? QUARKUS_REPEATED_PREFIX_FOO QUARKUS_REPEATED_PREFIX_BAR challenge assumptions
  22. @holly-cummins.bsky.social #RedHat load code onto a server then public static

    void main(String... args) { int exitCode = new CommandLine(new ModuleBuildDurationReport()).execute(args ); System.exit(exitCode); }
  23. @holly-cummins.bsky.social #RedHat load code onto a server then public static

    void main(String... args) { int exitCode = new CommandLine(new ModuleBuildDurationReport()).execute(args ); System.exit(exitCode); }
  24. @holly-cummins.bsky.social #RedHat cloud apps are immutable now CI public static

    void main(String... args) { int exitCode = new CommandLine(new ModuleBuildDurationReport()).execute(args ); System.exit(exitCode); }
  25. @holly-cummins.bsky.social #RedHat cloud apps are immutable now CI public static

    void main(String... args) { int exitCode = new CommandLine(new ModuleBuildDurationReport()).execute(args ); System.exit(exitCode); }
  26. @holly_cummins Java dynamism </> build time runtime load and parse

    • config files • properties • yaml • xml • etc.
  27. @holly_cummins Java dynamism @ @ </> build time runtime •

    classpath scanning and annotation discovery • attempt to load class to enable/disable features
  28. @holly_cummins what if we start the application more than once?

    @ @ </> @ @ </> @ @ </> @ @ </> so much work gets redone every time
  29. @holly-cummins.bsky.social JVM spends time loading classes for specific databases JVM

    class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database footprint example: Hibernate
  30. @holly-cummins.bsky.social JVM spends time loading classes for specific databases JVM

    class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database class for unused database turns out they’re never used footprint example: Hibernate
  31. @holly-cummins.bsky.social JVM spends time loading classes for specific databases JVM

    turns out they’re never used JIT spends time unloading classes footprint example: Hibernate
  32. @holly-cummins.bsky.social Hibernate example: ~500 classes which are only useful if

    you're running an Oracle database loaded and then unloaded
  33. @holly-cummins.bsky.social Hibernate example: ~500 classes which are only useful if

    you're running an Oracle database loaded and then unloaded every single start.
  34. @holly-cummins.bsky.social unused implementation the one we want interface unused implementation

    unused implementation the true cost of loaded classes isn’t just memory + start time method dispatching:
  35. @holly-cummins.bsky.social unused implementation the one we want interface unused implementation

    unused implementation the true cost of loaded classes isn’t just memory + start time method dispatching:
  36. @holly-cummins.bsky.social unused implementation the one we want interface megamorphic call

    slow dispatching unused implementation unused implementation the true cost of loaded classes isn’t just memory + start time method dispatching:
  37. @holly-cummins.bsky.social the true cost of loaded classes isn’t just memory

    + start time the one we want monomorphic call fast dispatching interface
  38. @holly_cummins @ @ </> build time runtime start • thread

    pools • I/O • etc. what if we initialize at build time?
  39. @holly_cummins @ @ </> build time runtime ready to do

    work! start • thread pools • I/O • etc. what if we initialize at build time?
  40. @holly-cummins.bsky.social implementation corollary: libraries must participate in the build process,

    not just the runtime process you need an extensible build process
  41. @holly_cummins #Quarkus #RedHat build items are communication mechanism between build

    steps framework automatically determines correct execution order and injects parameters
  42. @holly_cummins file reload live coding SASS changes detected, will rebuild:

    [META-INF/ resources/public/stylesheets/live.scss] Files changed but restart not needed - notified extensions in: 0.043s
  43. @holly_cummins file reload JVM agent reload live coding Application restart

    not required, replacing classes via instrumentation Live reload performed via instrumentation, no restart needed, total time: 0.180s
  44. @holly_cummins full restart file reload JVM agent reload live coding

    Restarting quarkus due to changes in Application$RenardeRequest.class, Application.class, Application$ApplicationGlobals.class, Application$Templates.class. Live reload total time: 1.415s
  45. @holly_cummins full restart file reload JVM agent reload not noticeable

    (quarkus starts fast) live coding Restarting quarkus due to changes in Application$RenardeRequest.class, Application.class, Application$ApplicationGlobals.class, Application$Templates.class. Live reload total time: 1.415s
  46. @holly-cummins.bsky.social how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection
  47. @holly-cummins.bsky.social how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection - give them a tight feedback loop
  48. @holly-cummins.bsky.social how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection - give them a tight feedback loop - for manual testing - for automated testing
  49. @holly-cummins.bsky.social how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection - give them a tight feedback loop - for manual testing - for automated testing - allow less typing
  50. @holly-cummins.bsky.social how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection - give them a tight feedback loop - for manual testing - for automated testing - allow less typing thank you, Java - strong typing - garbage collection
  51. @holly-cummins.bsky.social how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection - give them a tight feedback loop - for manual testing - for automated testing - allow less typing thank you, Java - strong typing - garbage collection we just covered this
  52. @holly-cummins.bsky.social most frameworks need to… -find all classes + interfaces

    + methods + fields annotated with @a -find all classes implementing or extending b
  53. @holly-cummins.bsky.social most frameworks need to… -find all classes + interfaces

    + methods + fields annotated with @a -find all classes implementing or extending b java doesn’t help us nothing in the reflection package does this
  54. @holly-cummins.bsky.social #RedHat package com.example; import org.jboss.logging.Logger; public class Thing {

    private static final Logger log = Logger.getLogger(Thing.class); public void doSomething() { log.info("It works!"); } } example: logging
  55. @holly-cummins.bsky.social #RedHat package com.example; import org.jboss.logging.Logger; public class Thing {

    private static final Logger log = Logger.getLogger(Thing.class); public void doSomething() { log.info("It works!"); } } example: logging import io.quarkus.logging.Log; Log
  56. @holly-cummins.bsky.social no. - use Jandex to find use-sites of the

    Log class “but isn’t that dynamism expensive?”
  57. @holly-cummins.bsky.social no. - use Jandex to find use-sites of the

    Log class - inject a static logger field $logger “but isn’t that dynamism expensive?”
  58. @holly-cummins.bsky.social no. - use Jandex to find use-sites of the

    Log class - inject a static logger field $logger - replace all calls of Log.method with calls to $logger.method “but isn’t that dynamism expensive?”
  59. @holly-cummins.bsky.social no. - use Jandex to find use-sites of the

    Log class - inject a static logger field $logger - replace all calls of Log.method with calls to $logger.method … all at build time “but isn’t that dynamism expensive?”
  60. @holly-cummins.bsky.social logging: compiled version public class MyService { // injected

    private static final Logger $logger = Logger.getLogger(Thing.class) public void doSomething() { $logger.info(“It works!”); } }
  61. @holly-cummins.bsky.social what if… you could inherit boilerplate Hibernate queries from

    a superclass, instead of having to write them all? example: hibernate
  62. @holly-cummins.bsky.social #RedHat @ApplicationScoped public class GreetingRepository { public Entity findByName(int

    name) { return find("name", name).firstResult(); } void persist(Entity entity) {} void delete(Entity entity) {} Entity findById(Id id) {} List<Entity> list(String query, Sort sort, Object... params) { return null; } Stream<Entity> stream(String query, Object... params) { return null; } long count() { return 0; } long count(String query, Object... params) { return 0; } } example: hibernate with panache
  63. @holly-cummins.bsky.social #RedHat example: hibernate with panache @ApplicationScoped public class GreetingRepository

    implements PanacheRepository<Greeting> { public Entity findByName(int name) { return find("name", name).firstResult(); } }
  64. @holly-cummins.bsky.social #RedHat DAO example: hibernate with panache @ApplicationScoped public class

    GreetingRepository implements PanacheRepository<Greeting> { public Entity findByName(int name) { return find("name", name).firstResult(); } } repository pattern
  65. @holly-cummins.bsky.social #RedHat example: hibernate with panache active record pattern @Entity

    public class Greeting extends PanacheEntity { public String name; public LocalDate issued; @Version public int version; public static List<Greeting> getTodaysGreetings() { return list("date", LocalDate.now()); } }
  66. @holly-cummins.bsky.social why was this even hard? public class PanacheEntity {

    public static <Entity extends PanacheEntity> List<Entity> listAll() { // but… how do we know which entity to query? throw new UnobtainiumException(); } }
  67. @holly-cummins.bsky.social why was this even hard? public class PanacheEntity {

    public static <Entity extends PanacheEntity> List<Entity> listAll() { // but… how do we know which entity to query? throw new UnobtainiumException(); } } signature can be generic
  68. @holly-cummins.bsky.social why was this even hard? public class PanacheEntity {

    public static <Entity extends PanacheEntity> List<Entity> listAll() { // but… how do we know which entity to query? throw new UnobtainiumException(); } } implementation cannot be generic signature can be generic
  69. @holly-cummins.bsky.social here’s what we do @Entity public class Order extends

    PanacheEntity { // … original class // injected in the bytecode // we add a Order.listAll method public static List<Order> listAll() { return DbOperations.listAll(Order.class); } }
  70. @holly-cummins.bsky.social #RedHat Energy 1 10 100 Time 1 10 100

    energy efficiency across programming languages Python Rust Java Go
  71. @holly-cummins.bsky.social #RedHat Energy 1 10 100 Time 1 10 100

    the trend line is more or less straight energy efficiency across programming languages Python Rust Java Go
  72. @holly_cummins #RedHat capacity Source: John O’Hara Setup: • REST +

    CRUD • large heap • RAPL energy measurement Assumptions: • US energy mix climate impact at low load (single instance) lower is better
  73. @holly_cummins #RedHat capacity Source: John O’Hara Setup: • REST +

    CRUD • large heap • RAPL energy measurement Assumptions: • US energy mix climate impact at low load (single instance) line length shows max throughput lower is better
  74. @holly_cummins #RedHat capacity Source: John O’Hara Setup: • REST +

    CRUD • large heap • RAPL energy measurement Assumptions: • US energy mix climate impact at low load (single instance) quarkus on JVM has the lowest carbon … because it has the highest throughput line length shows max throughput lower is better
  75. @holly_cummins #RedHat tl;dpa (too long didn’t pay attention) deployment density

    lower cloud bill frictionless development experience Medium Nano auto-provision services live coding continuous testing don’t make people tell the computer what the computer already knows greener reducing waste indexes moving work so it hurts less only doing work once