DEV Community

Cover image for A glimpse of K2 in nowinandroid
Iñaki Villar
Iñaki Villar

Posted on

A glimpse of K2 in nowinandroid

K2 is the new frontend for the Kotlin compiler. The compiler is already available for preview and is expected for the future release of Kotlin 2.0.
Last week, the official Android Developer blog published another article encouraging to start testing the new compiler. In this article, we will share the results of using K2 in the project nowinandroid.

The project

Scenarios
We have defined two main scenarios:

  • Clean builds
  • Incremental builds

The two variants used in both scenarios are main(branch) and k2, which is based on the main branch adding in gradle.properties:
kotlin.experimental.tryK2=true

Clean Builds
For each variant, we executed 100 parallel builds in GitHub runners for the task test using Gradle Enterprise API to retrieve the data.

The build execution for both variants was:

Main K2
Mean 15m 23s 15m 22s
Median 15m 28s 15m 21s

We didn't notice a significant difference. We understood it could be caused by the different tasks involved in the build execution. In the end, K2 affects only a subset of tasks in the build. We continued analyzing more in detail by aggregating the duration of the Kotlin-related tasks by type(ms):

Type Main K2
KotlinCompile Mean 2818 2800
KotlinCompile Median 1682 1812
KaptGenerateStubsTask Mean 853 1067
KaptGenerateStubsTask Median 515 669
KaptWithoutKotlincTask Mean 1049 1106
KaptWithoutKotlincTask Median 1682 1812

Still, the aggregation didn't show juicy data.

It's important to highlight that KSP and KAPT tasks currently fall back to using the old compiler when building your project with K2:

:feature:foryou:kaptGenerateStubsDemoReleaseKotlin

w: Kapt currently doesn't support language version 2.0+.

Falling back to 1.9.

For this reason, we focused only on the Kotlin Compiler task results.

We needed to have more detail in the analysis, so we picked the DemoDebug build variant and analyzed the median by task path:

Image description
At this point, we started seeing differences for specific tasks, in some cases positives, in others negative.
Taking those results, we represented the % of the duration difference relative to the main branch:

Image description
That gave us more hints to follow. To discard a possible outlier, we verified the worst and best results in the measurement:

  • ui-test:hilt-manifest
  • core:datastore

Image description

Image description

It looked good.

To understand better the origin of the differences, we used Kotlin Build Reports. This feature lets us get more detailed information about Kotlin compiler metrics. To enable it, add the property in gradle.properties:
kotlin.build.report.output=build_scan
Once we enable Kotlin Build Reports, a custom value is created in the Build Scan with the compiler information:

Image description

We aggregated the data of Kotlin Build Reports and provided the median for the tasks under investigation, worst-case:

Image description

:ui-test-hilt-manifest metrics gave significantly worse results for:

  • Compiler code analysis
  • Compiler code generation
  • Run compilation in Gradle worker
  • Sources compilation round

This module is used only in debugImplementation on the main app for the tests calling Composables that hook into the Dagger graph configuration. Additionally, I suspected that the kapt/hilt configuration could affect also.

best-case:
Image description

core:datastore:compileDemoDebugKotlin decrease the duration of:

  • Code generation lines per second
  • Compiler code analysis
  • Run compilation in Gradle Worker
  • Sources compilation round However, we observed an increase in duration for:
  • Compiler code generation

This module represents better a real Android module in our codebase, and we can confirm we got a 25% reduction in duration time for the task using K2. Great!

To finish this section, I found it interesting to share the metrics for other modules in the project using Kotlin Build Reports:

Image description

Image description

Image description

Image description

Image description

Incremental Builds
Using Gradle Profiler, we defined a scenario where we applied an incremental change to the class core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/NewsRepository.kt.
Each variant executed 50 builds for the task testDemoDebugUnitTest.

The results of analyzing the overall build time were:

Main K2
Mean 1m 4s 1m 20s
Median 1m 1s 1m 16s

We were slightly increasing the overall build time. We repeated the same process of the previous scenario aggregating duration grouped by the Kotlin Compiler task:

Type Main K2
KotlinCompile Mean 1267 1891
KotlinCompile Median 1033 1290

The gap between the mean and median led us to think that a performance issue occurred occurring during the K2 variant. We retrieved the GC time of the Kotlin process and confirmed the hypothesis:

Image description

Before pointing to a memory leak caused by using K2, it's fair to mention that these experiments are executed in free GitHub runners with limited resources. The available memory is 7 Gb using 3 Gb for each process(Kotlin/Gradle).
We reduced the number of iterations to 10 in the K2 variants and continued the investigation. After reducing the iterations, the results were:

Main K2
Mean 1267 1284
Median 1033 1076

This time, we have closer results. Then we visualized the data grouped by task path:
Image description

Representing the data by % Diff:
Image description

The K2 got better times in all the tasks of the incremental build except for :feature:interest and :app.

Using Kotlin Build Reports, we aggregated the task metrics for the best and worst tasks in the % diff results:

Image description

Image description

:core:data, the module that applies the incremental change, shows a decrease in duration for:

  • Compiler code analysis
  • Run compilation in Gradle Worker
  • Sources compilation round However, we observed an increase in duration for:
  • Compiler code generation

:feature:interest showed a 10% increase in aggregated build time. After analyzing the module, we didn't find a proper explanation of the behavior of this specific module compared with other feature modules.

Final Words
K2 is here and looks promising.
The project under experimentation doesn't represent a production project. The results in other projects may differ.
KAPT/KSP falling back to the old compiler may affect the final results, and we could expect better results once they are compatible.
So far, we have seen in this article that using K2 significantly improves Kotlin Compiler build time.

Happy Building!

Top comments (0)