KSP in Android projects
Tags: android, kotlin, gradle
Published: 2023-09-01 03:22:57

The Android community had awesome news this week: Dagger and Hilt KSP processors are now available in the latest release, v2.48:

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

 
The benefits of using KSP over kapt, in terms of build performance, are explained in the [official doc](https://kotlinlang.org/docs/ksp-why-ksp.html#comparison-to-kapt):

> The major advantages of KSP over kapt are improved build performance
> ...
> To run Java annotation processors unmodified, kapt compiles Kotlin code into Java stubs that retain information that Java annotation processors care about. To create these stubs, kapt needs to resolve all symbols in the Kotlin program. The stub generation costs roughly 1/3 of a full kotlinc analysis and the same order of kotlinc code-generation

Regarding Android projects, KSP was already used by different libraries, but Dagger/Hilt, one of the main DI frameworks used in the Android projects, required using kapt. Mixing KSP and kapt didn't bring benefits in terms of build performance. We were waiting anxiously to test a project using KSP exclusively.  

This article compares the results of building nowinadroid with KSP, using Dagger/Hilt 2.48, against the current configuration with kapt.

## The Project
As usual in these articles, the project under experimentation is [nowinandroid](https://github.com/android/nowinandroid). We are based on the main branch on this [commit](https://github.com/android/nowinandroid/commit/d0909a9c8f91153f0171d9f89e515a2f7df95202). Build stack components:
* Gradle 8.2
* AGP 8.1.0
* KGP 1.9.0
* Hilt 2.47

The main branch is one of the variants of this experiment, representing the kapt build. Currently, the kapt configuration is used on 21 projects defining the dependency:
* `com.google.dagger:hilt-android-compiler:2.47`

Then, we need a KSP branch acting as another variant in the experiment. We updated the required convention plugin to use KSP instead of kapt. Additionally, we had to apply small changes. The complete list of changes: https://github.com/cdsap/KspVsKapt/commit/5c7bab7b0241142f71caabde1d3558782db0bef4

## **Methodology**
We created two different scenarios:
* Clean builds: 50 builds per variant executed in parallel in GitHub Action runners. Task: `:app:assembleProdDebug`.
* Incremental change: 20 builds per variant executed in Github Action runners applying an incremental change on `core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/NewsRepository.kt` using Gradle Profiler.

We retrieved the build information with Gradle Enterprise API.

## Results
Before reviewing the build results, it's important to mention that replacing kapt with KSP affects the nature of the build. When applying the kapt plugin, we are adding two tasks to the project: 
* `org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask`
*  `org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask`

However, KSP adds only:
* `com.google.devtools.ksp.gradle.KspTaskJvm`

We will reduce the number of tasks on the KTS variant:

|                    | Tasks executed (kapt variant) | Tasks executed (KSP variant) |
|--------------------|-------------------------------|------------------------------|
| Clean Build        | 310                           | 292                          |
| Incremental Change | 164                           | 154                          |

**Clean Builds**
The build time, in seconds, for both variants was:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6mfzq7g0wthztpc497m.png)
We noticed that the configuration time took around 30% of the build time. Because the builds are executed in clean runners, we preferred to exclude the configuration time and reduce the noise from the configuration phase. The results were:

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

Still, this data included the execution of tasks unrelated to the kapt/KSP. We picked the modules implementing kapt/KSP and measured the task duration:
* For kapt variant, is the sum of `KaptGenerateStubsTask` + `KaptWithoutKotlincTask`
* For the KSP variant, it represents the duration of `KspTaskJvm`

The following view represents the median grouped by module of the projects using the plugins under investigation:

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

We noticed a general improvement in the build duration on each module, showing the `app` module a decrease of 60% in the processor duration.

**Incremental Builds**
Because an incremental change affects a specific tree of the task graph, we have fewer modules involved compared to the previous scenario. 
The configuration time is not a concern because we have incremental builds. The results after removing the warm-up builds:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f47l8u7w9p3pwt957il4.png)

Analyzing the median duration of the processor execution by module:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/na5g7jp0etzcoqvx6rte.png)

All modules decreased the processing time when using KSP. In this case, we didn't observe the same excellent results of clean builds, but we had significant wins.


## Final words
We showed that KSP decreases the processing build time in the project nowinandroid. The project is small, and all the task durations in both variants take seconds, far from the expensive kapt executions we are used to in real projects where it takes minutes in large modules. This first version is just the beginning because the release notes mention the following:

> Dagger’s KSP processors are still in the alpha stage. So far we’ve focused mainly on trying to ensure correctness rather than optimize performance. 

Exciting times and kudos to the Dagger/Hilt team.

Happy Building