Skip to content

Commit

Permalink
Update "Tips for improving Kotlin/Native compilation times" (#4575)
Browse files Browse the repository at this point in the history
  • Loading branch information
SvyatoslavScherbina authored Dec 2, 2024
1 parent 1f7cc02 commit 24904f7
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 71 deletions.
2 changes: 1 addition & 1 deletion docs/kr.tree
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@
<toc-element toc-title="Reference and tips">
<toc-element toc-title="Target support" topic="native-target-support.md"/>
<toc-element toc-title="App Store privacy manifests" topic="apple-privacy-manifest.md"/>
<toc-element toc-title="Improving compilation times" topic="native-improving-compilation-time.md"/>
<toc-element toc-title="Improving compilation time" topic="native-improving-compilation-time.md"/>
<toc-element toc-title="License files" topic="native-binary-licenses.md"/>
<toc-element topic="native-faq.md"/>
</toc-element>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ kotlin {
When you declare XCFrameworks, the Kotlin Gradle plugin will register several Gradle tasks:

* `assembleXCFramework`
* `assemble<Framework name>DebugXCFramework` (additionally debug artifact that contains [dSYMs](native-ios-symbolication.md))
* `assemble<Framework name>DebugXCFramework`
* `assemble<Framework name>ReleaseXCFramework`

<anchor name="build-frameworks"/>
Expand Down
210 changes: 141 additions & 69 deletions docs/topics/native/native-improving-compilation-time.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[//]: # (title: Tips for improving Kotlin/Native compilation times)
[//]: # (title: Tips for improving compilation time)

<show-structure depth="1"/>

The Kotlin/Native compiler is constantly receiving updates that improve its performance. With the latest Kotlin/Native
compiler and a properly configured build environment, you can significantly improve the compilation times of your projects
Expand All @@ -8,80 +10,150 @@ Read on for our tips on how to speed up the Kotlin/Native compilation process.

## General recommendations

* **Use the most recent version of Kotlin**. This way you will always have the latest performance improvements.
* **Avoid creating huge classes**. They take a long time to compile and load during execution.
* **Preserve downloaded and cached components between builds**. When compiling projects, Kotlin/Native downloads the required components
and caches some results of its work to the `$USER_HOME/.konan` directory. The compiler uses this directory for subsequent
compilations, making them take less time to complete.
### Use the latest version of Kotlin

This way, you always get the latest performance improvements. The most recent Kotlin version is %kotlinVersion%.

### Avoid creating huge classes

Try to avoid huge classes that take a long time to compile and load during execution.

### Preserve downloaded and cached components between builds

When compiling projects, Kotlin/Native downloads the required components
and caches some results of its work to the `$USER_HOME/.konan` directory. The compiler uses this directory for subsequent
compilations, making them take less time to complete.

When building in containers (such as Docker) or with continuous integration systems, the compiler may have to create
the `~/.konan` directory from scratch for each build. To avoid this step, configure your environment to preserve `~/.konan`
between builds. For example, redefine its location using the `kotlin.data.dir` Gradle property.
When building in containers (such as Docker) or with continuous integration systems, the compiler may have to create
the `~/.konan` directory from scratch for each build. To avoid this step, configure your environment to preserve `~/.konan`
between builds. For example, redefine its location using the `kotlin.data.dir` Gradle property.

Alternatively, you can use the `-Xkonan-data-dir` compiler option to configure your custom path to the directory via
the `cinterop` and `konanc` tools.
Alternatively, you can use the `-Xkonan-data-dir` compiler option to configure your custom path to the directory via
the `cinterop` and `konanc` tools.

## Gradle configuration

The first compilation with Gradle usually takes more time than subsequent ones due to the need to download the dependencies,
build caches, and perform additional steps. You should build your project at least twice to get an accurate reading of
the actual compilation times.

Here are some recommendations for configuring Gradle for better compilation performance:

* **Increase the [Gradle heap size](https://docs.gradle.org/current/userguide/performance.html#adjust_the_daemons_heap_size)**.
Add `org.gradle.jvmargs=-Xmx3g` to `gradle.properties`. If you use [parallel builds](https://docs.gradle.org/current/userguide/performance.html#parallel_execution),
you might need to choose the right number of workers with the `org.gradle.workers.max` property or the `--max-workers` command-line option.
The default value is the number of CPU processors.

* **Build only the binaries you need**. Don't run Gradle tasks that build the whole project, such as `build` or `assemble`,
unless you really need to. These tasks build the same code more than once, increasing the compilation times. In typical
cases such as running tests from IntelliJ IDEA or starting the app from Xcode, the Kotlin tooling avoids executing unnecessary
tasks.

If you have a non-typical case or build configuration, you might need to choose the task yourself.
* `linkDebug*`: To run your code during development, you usually need only one binary, so running the corresponding
`linkDebug*` task should be enough. Keep in mind that compiling a release binary (`linkRelease*`) takes more time
than compiling a debug one.
* `packForXcode`: Since iOS simulators and devices have different processor architectures, it's a common approach to
distribute a Kotlin/Native binary as a universal (fat) framework. During local development, it will be faster to build
the `.framework` for only the platform you're using.

To build a platform-specific framework, call the `packForXcode` task generated
by the [Kotlin Multiplatform project wizard](https://kmp.jetbrains.com/).

> Remember that in this case, you will need to clean the build using `./gradlew clean` after switching between the
> device and the simulator. See [this issue](https://youtrack.jetbrains.com/issue/KT-40907) for details.
>
{style="note"}


* **Don't disable the [Gradle daemon](https://docs.gradle.org/current/userguide/gradle_daemon.html)** without having a
good reason to. [Kotlin/Native runs from the Gradle daemon](https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-3-70-released/#kotlin-native)
by default. When it's enabled, the same JVM process is used and there is no need to warm it up for each compilation.

* **Don't use [transitiveExport = true](multiplatform-build-native-binaries.md#export-dependencies-to-binaries)**.
Using transitive export disables dead code elimination in many cases: the compiler has to process a lot of unused code. It increases the compilation time.
Use `export` explicitly for exporting the required projects and dependencies.

* **Use the Gradle [build caches](https://docs.gradle.org/current/userguide/build_cache.html)**:
* **Local build cache**: Add `org.gradle.caching=true` to your `gradle.properties` or run with `--build-cache` on the command line.
* **Remote build cache** in continuous integration environments. Learn how to [configure the remote build cache](https://docs.gradle.org/current/userguide/build_cache.html#sec:build_cache_configure_remote).

* **Enable previously disabled features of Kotlin/Native**. There are properties that disable the Gradle daemon and compiler
caches – `kotlin.native.disableCompilerDaemon=true` and `kotlin.native.cacheKind=none`. If you had issues with these
features before and added these lines to your `gradle.properties` or Gradle arguments, remove them and check whether
the build completes successfully. It is possible that these properties were added previously to work around issues that
have already been fixed.

* **Try incremental compilation of klib artifacts**. With incremental compilation, if only a part of the `klib` artifact
produced by the project module changes, just a part of `klib` is further
recompiled into a binary.

This feature is [Experimental](components-stability.md#stability-levels-explained). To enable it,
add the `kotlin.incremental.native=true` option to your `gradle.properties` file. If you face any problems,
create an [issue in YouTrack](https://kotl.in/issue).

## Windows OS configuration

* **Configure Windows Security**. Windows Security may slow down the Kotlin/Native compiler. You can avoid this by adding the `.konan` directory, which is located in `%\USERPROFILE%` by default, to Windows Security exclusions. Learn how to [add exclusions to Windows Security](https://support.microsoft.com/en-us/windows/add-an-exclusion-to-windows-security-811816c0-4dfd-af4a-47e4-c301afe13b26).
Below are some recommendations for configuring Gradle for better compilation performance.

### Increase Gradle heap size

To increase the [Gradle heap size](https://docs.gradle.org/current/userguide/performance.html#adjust_the_daemons_heap_size),
add `org.gradle.jvmargs=-Xmx3g` to your `gradle.properties` file.

If you use [parallel builds](https://docs.gradle.org/current/userguide/performance.html#parallel_execution),
you might need to choose the right number of workers with the `org.gradle.workers.max` property or the `--max-workers` command-line option.
The default value is the number of CPU processors.

### Build only necessary binaries

Don't run Gradle tasks that build the whole project, such as `build` or `assemble`, unless you really need to.
These tasks build the same code more than once, increasing the compilation times. In typical cases, such as running tests
from IntelliJ IDEA or starting the app from Xcode, the Kotlin tooling avoids executing unnecessary tasks.

If you have a non-typical case or build configuration, you might need to choose the task yourself:

* `linkDebug*`. To run your code during development, you usually need only one binary, so running the corresponding
`linkDebug*` task should be enough.
* `embedAndSignAppleFrameworkForXcode`. Since iOS simulators and devices have different processor architectures,
it's a common approach to distribute a Kotlin/Native binary as a universal (fat) framework.

However, during local development, it's faster to build the `.framework` file only for the platform you're using.
To build a platform-specific framework, use the [embedAndSignAppleFrameworkForXcode](multiplatform-direct-integration.md#connect-the-framework-to-your-project) task.

### Build only for necessary targets

Similarly to the recommendation above, don't build a binary for all native
platforms at once. For example, compiling an [XCFramework](multiplatform-build-native-binaries.md#build-xcframeworks)
(using an `*XCFramework` task) builds the same code for all targets, which takes proportionally more time than
building for a single target.

If you do need XCFrameworks for your setup, you can reduce the number of targets.
For example, you don't need `iosX64` if you don't run this project on iOS simulators on Intel-based Macs.

> Binaries for different targets are built with `linkDebug*$Target` and `linkRelease*$Target` Gradle tasks.
> You can look for the executed tasks in the build log or in the
> [Gradle build scan](https://docs.gradle.org/current/userguide/build_scans.html)
> by running a Gradle build with the `--scan` option.
>
{style="tip"}

### Don't build unnecessary release binaries

Kotlin/Native supports two build modes, [debug and release](multiplatform-build-native-binaries.md#declare-binaries).
Release is highly optimized, and this takes a lot of time: compilation of release binaries takes an order of magnitude
more time than debug binaries.

Apart from an actual release, all these optimizations might be unnecessary in a typical development cycle.
If you use a task with `Release` in its name during the development process, consider replacing it with `Debug`.
Similarly, instead of running `assembleXCFramework`, you can run `assembleSharedDebugXCFramework`, for example.

> Release binaries are built with `linkRelease*` Gradle tasks. You can check for them in the build log
> or in the [Gradle build scan](https://docs.gradle.org/current/userguide/build_scans.html) by running a Gradle build
> with the `--scan` option.
>
{style="tip"}

### Don't disable Gradle daemon

Don't disable the [Gradle daemon](https://docs.gradle.org/current/userguide/gradle_daemon.html) without having a good reason. By default, [Kotlin/Native runs from the Gradle daemon](https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-3-70-released/#kotlin-native).
When it's enabled, the same JVM process is used, and there is no need to warm it up for each compilation.

### Don't use transitive export

Using [`transitiveExport = true`](multiplatform-build-native-binaries.md#export-dependencies-to-binaries) disables dead
code elimination in many cases, so the compiler has to process a lot of unused code. It increases the compilation time.
Instead, use the `export` method explicitly for exporting the required projects and dependencies.

### Don't export modules too much

Try to avoid unnecessary [module export](multiplatform-build-native-binaries.md#export-dependencies-to-binaries).
Each exported module negatively affects compilation time and binary size.

### Use Gradle build caching

Enable the Gradle [build cache](https://docs.gradle.org/current/userguide/build_cache.html) feature:

* **Local build cache**. For local caching, add `org.gradle.caching=true` to your `gradle.properties` file or run the
build with the `--build-cache` option in the command line.
* **Remote build cache**. Learn how to [configure the remote build cache](https://docs.gradle.org/current/userguide/build_cache.html#sec:build_cache_configure_remote)
for continuous integration environments.

### Use Gradle configuration cache

To use the Gradle [configuration cache](https://docs.gradle.org/current/userguide/configuration_cache.html),
add `org.gradle.configuration-cache=true` to your `gradle.properties` file.

> Configuration cache also enables running `link*` tasks in parallel which could heavily load the machine,
> specifically with a lot of CPU cores. This issue will be fixed in [KT-70915](https://youtrack.jetbrains.com/issue/KT-70915).
>
{style="note"}

### Enable previously disabled features

There are Kotlin/Native properties that disable the Gradle daemon and compiler caches:

* `kotlin.native.disableCompilerDaemon=true`
* `kotlin.native.cacheKind=none`
* `kotlin.native.cacheKind.$target=none`, where `$target` is a Kotlin/Native compilation target, for example `iosSimulatorArm64`.

If you had issues with these features before and added these lines to your `gradle.properties` file or Gradle arguments,
remove them and check whether the build completes successfully. It is possible that these properties were added previously
to work around issues that have already been fixed.

### Try incremental compilation of klib artifacts

With incremental compilation, if only a part of the `klib` artifact produced by the project module changes,
just a part of `klib` is further recompiled into a binary.

This feature is [Experimental](components-stability.md#stability-levels-explained). To enable it,
add the `kotlin.incremental.native=true` option to your `gradle.properties` file. If you face any problems,
create an [issue in YouTrack](https://kotl.in/issue).

## Windows configuration

Windows Security may slow down the Kotlin/Native compiler. You can avoid this by adding the `.konan` directory,
which is located in `%\USERPROFILE%` by default, to Windows Security exclusions. Learn how to [add exclusions to Windows Security](https://support.microsoft.com/en-us/windows/add-an-exclusion-to-windows-security-811816c0-4dfd-af4a-47e4-c301afe13b26).

0 comments on commit 24904f7

Please sign in to comment.