A glimpse of K2 in nowinandroid
Tags: android, kotlin, gradle
Published: 2023-08-09 21:29:45

[K2](https://blog.jetbrains.com/kotlin/2023/02/k2-kotlin-2-0/) 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](https://android-developers.googleblog.com/2023/07/try-k2-compiler-in-your-android-projects.html) encouraging to start testing the new compiler. In this article, we will share the results of using K2 in the project nowinandroid. 

**The project**

* [nowinandroid](https://github.com/android/nowinandroid)
* Commit: [0a20cb7]
(https://github.com/android/nowinandroid/commit/0a20cb729b871514c056d91aa05244e0bfd86d24) (Aug 2, 2023)
* Gradle 8.2
* AGP 8.1.0
* KGP 1.9.0

**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](https://docs.gradle.com/enterprise/api-manual/) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufw9maeufcjl7s8yw0py.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ucm00bg9idpbayn1s1z.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dldkqhxtt7rxc9w3kxiy.png)



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l151gi7i3pfs2qaxfe6c.png)

It looked good. 

To understand better the origin of the differences, we used [Kotlin Build Reports](https://blog.jetbrains.com/kotlin/2022/06/introducing-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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fxo825fqznjnnt6ny48p.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hg22aisokqr3sgx3yp1.png)

`: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](https://github.com/android/nowinandroid/blob/main/app/build.gradle.kts#L107) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/il8u5j4mbgbngf7uhkwp.png)

`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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38wtgb4b6acug869r8cp.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5kbggxzjruzrd6puyx1.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28thneyqxwtujanuqc0j.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0pz46tgiavhxhox5y2q.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uicirarqu71cyhlqwdhz.png)



**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](https://github.com/cdsap/InfoKotlinProcess) and confirmed the hypothesis:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lurn423rvxhgd48bcarb.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m85k0kphapx8ubcrvw9w.png)

Representing the data by % Diff:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/spp34rup0pzormq8mt5a.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c2nbwaowclborj1w2g64.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwutjy3fz8ctc01oblgs.png)


`: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!