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

Taming the Kotlin Compiler (Droidcon TO 2019)

Taming the Kotlin Compiler (Droidcon TO 2019)

As more and more Android code is being written in Kotlin, and many developers are switching over from Java, the performance of the Kotlin compiler has become a hot discussion topic. Javac has been tuned and optimized for many years, so how does kotlinc compare?

In addition, there are a whole new family of tools around kotlinc, such as kapt, the Kotin Gradle plugin and more. How can one use them efficiently to get optimal performance?

In this talk, we’ll take a deep dive into the following:

* Is Kotlin slower than Java? Should you worry?
* Caching and incrementality of Kotlin compilation
* How to use Kotlin with Gradle efficiently
* What is compilation avoidance and what are the state of things in Kotlin?
* Kotlin compilation in the context of Android

This talk will help you develop a better understanding of how your code is built, what performance bottlenecks exist in the toolchain, and how to avoid them to improve your build times.

Co-Presented with @Egorand.

Video: coming soon!

6c8b509fe5422470d148c2c4cf2eb4b0?s=128

John Rodriguez

November 13, 2019
Tweet

Transcript

  1. Taming the Kotlin Compiler

  2. None
  3. $ git log commit 59da358e7aef1109fb71d964f846e9dcb61517c6 Author: Matt Precious <…@squareup.com> Date:

    Fri Nov 10 10:47:32 2017 Activity contact header tweaks $ find . -name *.java | wc -l 1122 $ find . -name *.kt | wc -l 330
  4. $ git log commit c9ba9967bfdb3cdde63a336ef91001040b57fed0 Author: Andrew Tran <…@squareup.com> Date:

    Fri Nov 8 15:04:30 2019 Updating selection blocker to respect help items $ find . -name *.java | wc -l 209 $ find . -name *.kt | wc -l 2611
  5. None
  6. None
  7. None
  8. for(i in 1..N) {a // write N java files }a

  9. for(i in 1..N) {a val javaclassfile = File(javaSourcesDir, "JavaTemp$i.java") javaclassfile.use

    {bout -> out.println("class JavaTemp$i {}") }b }a
  10. for(i in 1..N) {a val javaclassfile = File(javaSourcesDir, "JavaTemp$i.java") javaclassfile.use

    {bout -> out.println("class JavaTemp$i {}") }b val process = ProcessBuilder() .command("time", "javac", "…") .start() val timeOutput = process.errorStream.use(BufferedReader::readLine) val tokens = timeOutput.split("\\s+".toRegex()) outputLogger.use {blog -> log.write("$i\t${tokens[1]}\t${tokens[3]}\t${tokens[5]}\n") }b }a
  11. for(i in 1..N) {a appendWriter.use {csources -> val javaclassfile =

    File(javaSourcesDir, "JavaTemp$i.java") javaclassfile.use {bout -> out.println("class JavaTemp$i {}") }b sources.write("$javaclassfile\n") }c val process = ProcessBuilder() .command("time", "javac", "@javasources/sources") .start() val timeOutput = process.errorStream.use(BufferedReader::readLine) val tokens = timeOutput.split("\\s+".toRegex()) outputLogger.use {blog -> log.write("$i\t${tokens[1]}\t${tokens[3]}\t${tokens[5]}\n") }b }a
  12. for(i in 1..N) {a appendWriter.use {csources -> val ktclassfile =

    File(ktSourcesDir, "KotlinTemp$i.kt") ktclassfile.use {bout -> out.println("class KotlinTemp$i {}") }b sources.write("$ktclassfile\n") }c val process = ProcessBuilder() .command("time", "kotlinc", "@ktsources/sources") .start() val timeOutput = process.errorStream.use(BufferedReader::readLine) val tokens = timeOutput.split("\\s+".toRegex()) outputLogger.use {blog -> log.write("$i\t${tokens[1]}\t${tokens[3]}\t${tokens[5]}\n") }b }a
  13. None
  14. None
  15. for(i in 1..N) {a appendWriter.use {csources -> val javaclassfile =

    File(javaSourcesDir, "JavaTemp$i.java") javaclassfile.use {bout -> out.println("class JavaTemp$i {}") }b sources.write("$javaclassfile\n") }c val process = ProcessBuilder() .command("time", "javac", "@javasources/sources") .start() val timeOutput = process.errorStream.use(BufferedReader::readLine) val tokens = timeOutput.split("\\s+".toRegex()) outputLogger.use {blog -> log.write("$i\t${tokens[1]}\t${tokens[3]}\t${tokens[5]}\n") }b }a
  16. for (( i=1; i<="$N"; i++ )) do JAVACLASSFILE="$SOURCES/JavaTemp$i.java" echo "package

    app.cash.droidcon.to;" >> "$JAVACLASSFILE" echo "class JavaTemp$i {}" >> "$JAVACLASSFILE" TIMED=$(time (./gradlew java:assemble > /dev/null) 2>&1) DATA=$(echo -n "$TIMED" | cut -f2 -d'm' | cut -f1 -d's' | tr '\n' '\t') echo "$i$DATA" >> "$OUTPUT" done
  17. None
  18. None
  19. None
  20. None
  21. None
  22. https://bit.ly/32DOCyf

  23. None
  24. None
  25. CardCustomizationData

  26. TouchData CardCustomizationData CardTheme

  27. TouchData Stroke StampCustomization CardCustomizationData CardTheme

  28. TouchData Stroke StampCustomization Point CardCustomizationData CardTheme

  29. None
  30. None
  31. None
  32. :presenters :backend:api :app :backend:jvm https://git.io/JervG

  33. :presenters :backend:api :app :backend:jvm https://git.io/JervG kotlin-jvm com.android.library kotlin-android com.android.library kotlin-android

    kotlin-kapt com.android.application kotlin-android kotlin-kapt
  34. kapt?

  35. None
  36. http://bit.ly/2CDcSWx

  37. None
  38. :presenters :backend:api :app :backend:jvm

  39. None
  40. kaptGenerateStubsDebugKotlin kaptDebugKotlin compileDebugKotlin

  41. kaptGenerateStubsDebugKotlin kaptDebugKotlin compileDebugKotlin

  42. class RealGreeter @Inject constructor() : Greeter { override fun greeting(name:

    String): Observable<Greeting> { return Observable .just(Greeting(message = "Hello $name!")) .delay(2, SECONDS) } } kaptGenerateStubsDebugKotlin
  43. class RealGreeter @Inject constructor() : Greeter { override fun greeting(name:

    String): Observable<Greeting> { return Observable .just(Greeting(message = "Hello $name!")) .delay(2, SECONDS) } } @kotlin.Metadata(…) public final class RealGreeter implements Greeter { @NotNull @Override public Observable<Greeting> greeting(@NotNull String name) { return null; } @Inject public RealGreeter() { super(); } } kaptGenerateStubsDebugKotlin
  44. class RealGreeter @Inject constructor() : Greeter { override fun greeting(name:

    String): Observable<Greeting> { return Observable .just(Greeting(message = "Hello $name!")) .delay(2, SECONDS) } } @kotlin.Metadata(…) public final class RealGreeter implements Greeter { @NotNull @Override public Observable<Greeting> greeting(@NotNull String name) { return null; } @Inject public RealGreeter() { super(); } } kaptGenerateStubsDebugKotlin ABI = Application Binary Interface
  45. class RealGreeter @Inject constructor() : Greeter { val publicField: Foo

    override fun greeting(name: String): Observable<Greeting> { … } } Application Binary Interface
  46. class RealGreeter @Inject constructor() : Greeter { val publicField: Foo

    override fun greeting(name: String): Observable<Greeting> { … } } Application Binary Interface
  47. kaptGenerateStubsDebugKotlin kaptDebugKotlin compileDebugKotlin

  48. kaptGenerateStubsDebugKotlin kaptDebugKotlin compileDebugKotlin

  49. @kotlin.Metadata(…) public final class RealGreeter implements Greeter { @NotNull @Override

    public Observable<Greeting> greeting(@NotNull String name) { return null; } @Inject public RealGreeter() { super(); } } kaptDebugKotlin public final class GreeterPresenter { @Inject Greeter greeter; public void present() {…} }
  50. kaptGenerateStubsDebugKotlin kaptDebugKotlin compileDebugKotlin

  51. kaptGenerateStubsDebugKotlin kaptDebugKotlin compileDebugKotlin

  52. kaptGenerateStubsDebugKotlin kaptDebugKotlin compileDebugKotlin

  53. None
  54. Why are my builds slow?

  55. $ ./gradlew :app:assembleDebug BUILD SUCCESSFUL in 19s See the profiling

    report at: file:///Users/egor/.../cash/build/reports/ profile/profile-2019-11-11-11-33-51.html --profile
  56. None
  57. None
  58. $ echo kotlin.build.report.enable=true >> ~/.gradle/gradle.properties $ ./gradlew :app:assembleDebug $ echo

    kotlin.build.report.verbose=true >> ~/.gradle/gradle.properties Kotlin build report is written to /Users/egor/…/DCTO19CashApp/build/ reports/kotlin-build/DCTO19CashApp-build-2019-11-11-11-37-23.txt BUILD SUCCESSFUL in 19s
  59. None
  60. None
  61. Build cache Since Kotlin 1.2.21

  62. None
  63. $ ./gradlew clean $ ./gradlew app:assembleDebug

  64. None
  65. None
  66. Incremental compilation Since Kotlin 1.1.1

  67. Incremental kapt Since Kotlin 1.3.30 Incremental compilation Since Kotlin 1.1.1

  68. $ ./gradlew :app:assembleDebug $ echo “class Test2” >> app/src/…/MainActivity.kt $

    ./gradlew :app:assembleDebug
  69. None
  70. None
  71. None
  72. None
  73. None
  74. Non-incremental compilation

  75. $ ./gradlew clean $ ./gradlew :app:assembleDebug $ echo “class Test3”

    >> app/src/…/MainActivity.kt $ ./gradlew :app:assembleDebug // FROM-CACHE
  76. None
  77. None
  78. None
  79. None
  80. None
  81. None
  82. None
  83. http://bit.ly/34ZN5UQ

  84. https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/ jetbrains/kotlin/gradle/tasks/Tasks.kt#L128

  85. Compilation avoidance

  86. If a dependency has changed in an ABI- compatible way

    (only its private API has changed), then Java compilation tasks will be up to-date. This means that if project A depends on project B and a class in B is changed in an ABI-compatible way (typically, changing only the body of a method), then Gradle won't recompile A.
  87. :app :presenters implementation // Add “private val a = 1”

    to presenters/ …/GreeterPresenter.kt $ ./gradlew :app:assembleDebug
  88. None
  89. None
  90. None
  91. None
  92. None
  93. None
  94. None
  95. None
  96. None
  97. How do I debug my builds?

  98. kotlin.build.report.enable=true kotlin.build.report.verbose=true … $ ./gradlew :app:assembleDebug Kotlin build report is

    written to /Users/egor/…/DCTO19CashApp/build/ reports/kotlin-build/DCTO19CashApp-build-2019-11-11-11-37-23.txt BUILD SUCCESSFUL in 19s ~/.gradle/gradle.properties
  99. gradle.taskGraph.afterTask { task -> println " inputs: " task.inputs.files.each {

    println " " + it } println " outputs: " task.outputs.files.each { println " " + it } } ~/.gradle/gradle.properties
  100. > Task :app:kaptGenerateStubsDebugKotlin inputs: … outputs: …/app/build/tmp/kapt3/incrementalData/debug …/app/build/tmp/kapt3/stubs/debug > Task

    :app:kaptDebugKotlin inputs: … outputs: …/app/build/tmp/kapt3/classes/debug …/app/build/generated/source/kapt/debug …/app/build/generated/source/kaptKotlin/debug > Task :app:compileDebugKotlin inputs: … outputs: …/app/build/tmp/kotlin-classes/debug
  101. $ fswatch -xrt \ app/build/tmp/kapt3/incrementalData/debug \ app/build/tmp/kapt3/stubs/debug \ app/build/tmp/kapt3/classes/debug \

    app/build/generated/source/kapt/debug \ app/build/generated/source/kaptKotlin/debug \ app/build/tmp/kotlin-classes/debug
  102. $ fswatch -xrt \ app/build/tmp/kapt3/incrementalData/debug \ app/build/tmp/kapt3/stubs/debug \ app/build/tmp/kapt3/classes/debug \

    app/build/generated/source/kapt/debug \ app/build/generated/source/kaptKotlin/debug \ app/build/tmp/kotlin-classes/debug $ ./gradlew app:aDebug
  103. …/app/build/tmp/kotlin-classes/debug/…/cashapp/GreeterView.class Removed IsFile …/app/build/tmp/kotlin-classes/debug/…/cashapp Removed IsDir …/app/build/tmp/kotlin-classes/debug/…/cashapp Created IsDir …/app/build/tmp/kotlin-classes/debug/…/cashapp/GreeterView.class

    Created Updated IsFile
  104. None
  105. https://bit.ly/2CI8GF7

  106. None
  107. None
  108. @jrodbx @egorand