Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Zero Waste, Radical Magic, and Italian Graft – ...

Zero Waste, Radical Magic, and Italian Graft – Quarkus Efficiency Secrets

What makes a platform efficient? Is it how quickly code executes, or is it how quickly developers can use it to solve problems? 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? 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

April 08, 2024
Tweet

More Decks by Holly Cummins

Other Decks in Programming

Transcript

  1. Holly Cummins QCon London April 9, 2024 Zero Waste, Radical

    Magic and Italian Graft Quarkus Efficiency Secrets
  2. @holly_cummins “Rust is the hardest programming language up to that

    time I’ve met.” -Michael Vaner https://vorner.github.io/difficult.html
  3. @holly_cummins Enter … Quarkus. #RedHat A Java framework that gets

    you going faster, faster. can we do better?
  4. Quarkus applications start fast Quarkus + graalvm 0.014 Seconds REST

    application Quarkus + open jdk 0.75 Seconds traditional cloud-native stack 4.3 Seconds https://Quarkus.io/blog/runtime-performance/
  5. @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
  6. @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)
  7. @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/
  8. 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
  9. 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
  10. 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
  11. 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
  12. @holly_cummins Java dynamism </> build time runtime load and parse

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

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

    @ @ </> @ @ </> @ @ </> @ @ </> so much work gets redone every time
  15. @holly_cummins 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
  16. @holly_cummins 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
  17. @holly_cummins JVM spends time loading classes for specific databases JVM

    turns out they’re never used JIT spends time unloading classes footprint example: Hibernate
  18. @holly_cummins Hibernate example: ~500 classes which are only useful if

    you're running an Oracle database loaded and then unloaded
  19. @holly_cummins Hibernate example: ~500 classes which are only useful if

    you're running an Oracle database loaded and then unloaded every single start.
  20. @holly_cummins 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:
  21. @holly_cummins 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:
  22. @holly_cummins 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:
  23. @holly_cummins the true cost of loaded classes isn’t just memory

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

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

    work! start • thread pools • I/O • etc. what if we initialize at build time?
  26. @holly_cummins doing more up-front - speeds up start - shrinks

    memory footprint - improves throughput (!)
  27. @holly_cummins implementation corollary: libraries must participate in the build process,

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

    steps framework automatically determines correct execution order and injects parameters
  29. @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
  30. @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
  31. @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
  32. @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
  33. @holly_cummins how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection
  34. @holly_cummins how to make people efficient - make it hard

    to get wrong - strong typing - garbage collection - give them a tight feedback loop
  35. @holly_cummins 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
  36. @holly_cummins 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
  37. @holly_cummins 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
  38. @holly_cummins 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
  39. @holly_cummins most frameworks need to… -find all classes + interfaces

    + methods + fields annotated with @X -find all classes implementing or extending X
  40. @holly_cummins most frameworks need to… -find all classes + interfaces

    + methods + fields annotated with @X -find all classes implementing or extending X Java doesn’t help us nothing in the reflection package does this
  41. @holly_cummins #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
  42. @holly_cummins #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
  43. @holly_cummins no. - use Jandex to find use-sites of the

    Log class “but isn’t that dynamism expensive?”
  44. @holly_cummins no. - use Jandex to find use-sites of the

    Log class - inject a static logger field $logger “but isn’t that dynamism expensive?”
  45. @holly_cummins 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?”
  46. @holly_cummins 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?”
  47. @holly_cummins logging: compiled version public class MyService { // injected

    private static Logger $logger = Logger.getLogger(Thing.class) public void doSomething() { $logger.info(“It works!”); } }
  48. @holly_cummins what if… you could inherit boilerplate Hibernate queries from

    a superclass, instead of having to write them all? example: hibernate
  49. @holly_cummins #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
  50. @holly_cummins #RedHat example: hibernate with panache @ApplicationScoped public class GreetingRepository

    implements PanacheRepository<Greeting> { public Entity findByName(int name) { return find("name", name).firstResult(); } }
  51. @holly_cummins #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
  52. @holly_cummins #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()); } }
  53. @holly_cummins 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(); } }
  54. @holly_cummins 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
  55. @holly_cummins 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
  56. @holly_cummins 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); } }
  57. @holly_cummins developer-zero on Quarkus redesigned Hibernate to “boot in advance”

    what is the common factor behind our performance improvements?
  58. @holly_cummins hyper-focussed performance engineer delivers big fixes to many open

    source Java projects what is the common factor behind our performance improvements?
  59. @holly_cummins what is the common factor behind our performance improvements?

    working on neighbouring projects big improvement to efficiency of Jackson with virtual threads
  60. @holly_cummins #RedHat A lot of clever people made Quarkus so

    efficient. Only some of them were Italian.
  61. @holly_cummins #RedHat Emiliia Nesterovych Emmanuel Bernard Emre Kaplan Enrique gonzález

    Martínez Enrique Mingorance Cano Eoin Gallinagh Eric Deandrea Eric Wittmann Erik Åsén Erik Mattheis Erin Schnabel Eugene Berman Evan Shortiss Fabricio Gregorio faculbsz Falko Modler Fedor Dudinskiy Felipe Carvalho dos Anjos Formentin Felipe Henrique Gross Windmoller Fernando Comunello Fernando Henrique fhavel Fikru Mengesha Filippe Spolti Florian Beutel Florian Bütler Florian Heubeck Florin Botis Foivos Zakkak Foobartender Fouad Almalki Francesco Nigro Francisco Javier Tirado Sarti Francois Steyn Frank Eichfelder franz1981 freakse-sa Fred Bricon Frédérc Blanc Freeman Fang Fu Cheng Gabriele Cardosi Galder Zamarreño galiacheng Gavin King Gavin Ray Geert Schuring Geoffrey De Smet Geoffrey GREBERT Georg Leber George Gastaldi manofthepeace Manyanda Chitimbo Marat Gubaidullin Marc Nuri Marc Schlegel Marc Wrobel Marcel Hanser Marcel Lohmann Marcell Cruz Marcelo Pereira Marcin Czeczko Marcin Kłopotek Marco Bungart Marco Schaub Marco Yeung Marco Zanghì Marcus Paulo Marek goldmann Marek Skacelik Marián Macik Mario Fusco MarioHNogueira Mark Lambert Mark Little Mark McLaughlin Mark Sailes marko-bekhta Markus Heberling Markus Himmel Markus Schwer Martin C. Richards Martin Grammelspacher Martin Kouba Martin Muzikar Martin Panzer Martin Weiler martin-kofoed-jyskebank-dk MartinWitt Marvin B. Lillehaug masini Matej Novotny Matej Vasek Matheus Cruz Mathias Holzer Matteo Mortari Matthias Andreas Benkard Matthias Cullmann mauroal Max Andersen Max Gabrielsson Max Rydahl Andersen Victor Hugo de Oliveira Molinar Vincent Sevel Vincent van Dam Vinícius Ferraz Campos Florentino Viswa Teja Nariboina Vladimir Konkov Vojtech Juranek Vratislav Hais w.glanzer Walter Medvedeo Wayne Ellis Werner Glanzer Willem Jan Glerum William Antônio Siqueira Wim goeman Wippermueller, Frank wojciech.stryjewski Xavier Xieshen xstefank Y. Luis Yann-Thomas LE MOIGNE Yannick Reifschneider YassinHajaj Yelzhas Suleimenov yesunch9 Yoann Rodière Yoshikazu Nojima Youngmin Koo Yubao Liu yugoccp Yukihiro Okada Zaheed Beita zanmagerl zedbeit Zheng Feng Žiga Deisinger Zineb Bendhiba zohar Zoran Regvart Шумов Игорь Юрьевич 出 门 三不惹 A lot of clever people made Quarkus so efficient. Only some of them were Italian.
  62. @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 : !!
  63. @holly_cummins #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
  64. @holly_cummins #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
  65. @holly_cummins #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 …
  66. @holly_cummins #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 …
  67. @holly_cummins #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 …
  68. @holly_cummins #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 …
  69. @holly_cummins #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 …
  70. @holly_cummins #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 …
  71. @holly_cummins #RedHat a statistics story: how we missed regressions a

    parametric change-detection algorithm meant this big regression masked other, smaller, regressions
  72. @holly_cummins #RedHat string comparison: who says you have to read

    strings left-to-right? challenge assumptions
  73. @holly_cummins #RedHat string comparison: who says you have to read

    strings left-to-right? QUARKUS_REPEATED_PREFIX_FOO QUARKUS_REPEATED_PREFIX_BAR challenge assumptions
  74. @holly_cummins #RedHat string comparison: who says you have to read

    strings left-to-right? QUARKUS_REPEATED_PREFIX_FOO QUARKUS_REPEATED_PREFIX_BAR challenge assumptions
  75. @holly_cummins #RedHat Energy 1 10 100 Time 1 10 100

    energy efficiency across programming languages Python Rust Java Go
  76. @holly_cummins #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
  77. @holly_cummins #RedHat capacity Source: John O’Hara Setup: • REST +

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

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

    CRUD • large heap • RAPL energy measurement Assumptions: 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
  80. @holly_cummins efficient languages machine efficiency human efficiency (too long; didn’t

    pay attention) tl;dpa challenge assumptions only do work once
  81. @holly_cummins efficient languages machine efficiency human efficiency (too long; didn’t

    pay attention) tl;dpa challenge assumptions only do work once move work to where it hurts least
  82. @holly_cummins efficient languages machine efficiency human efficiency (too long; didn’t

    pay attention) tl;dpa challenge assumptions only do work once move work to where it hurts least index, index, index
  83. @holly_cummins efficient languages machine efficiency human efficiency (too long; didn’t

    pay attention) tl;dpa challenge assumptions only do work once move work to where it hurts least index, index, index efficiency needs continued investment
  84. @holly_cummins efficient languages machine efficiency human efficiency (too long; didn’t

    pay attention) tl;dpa challenge assumptions challenge assumptions only do work once move work to where it hurts least index, index, index efficiency needs continued investment
  85. @holly_cummins efficient languages machine efficiency human efficiency (too long; didn’t

    pay attention) tl;dpa challenge assumptions challenge assumptions tighten feedback loops only do work once move work to where it hurts least index, index, index efficiency needs continued investment
  86. @holly_cummins efficient languages machine efficiency human efficiency (too long; didn’t

    pay attention) tl;dpa challenge assumptions challenge assumptions tighten feedback loops only do work once move work to where it hurts least index, index, index efficiency needs continued investment don’t make humans tell the computer what the computer already knows