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!

John Rodriguez

November 13, 2019
Tweet

More Decks by John Rodriguez

Other Decks in Technology

Transcript

  1. $ 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
  2. $ 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. class RealGreeter @Inject constructor() : Greeter { override fun greeting(name:

    String): Observable<Greeting> { return Observable .just(Greeting(message = "Hello $name!")) .delay(2, SECONDS) } } kaptGenerateStubsDebugKotlin
  9. 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
  10. 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
  11. class RealGreeter @Inject constructor() : Greeter { val publicField: Foo

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

    override fun greeting(name: String): Observable<Greeting> { … } } Application Binary Interface
  13. @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() {…} }
  14. $ ./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
  15. $ 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
  16. $ ./gradlew clean $ ./gradlew :app:assembleDebug $ echo “class Test3”

    >> app/src/…/MainActivity.kt $ ./gradlew :app:assembleDebug // FROM-CACHE
  17. 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.
  18. :app :presenters implementation // Add “private val a = 1”

    to presenters/ …/GreeterPresenter.kt $ ./gradlew :app:assembleDebug
  19. 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
  20. gradle.taskGraph.afterTask { task -> println " inputs: " task.inputs.files.each {

    println " " + it } println " outputs: " task.outputs.files.each { println " " + it } } ~/.gradle/gradle.properties
  21. > 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
  22. $ 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
  23. $ 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