This project contains performance benchmarks for Compose Multiplatform on various targets, including Desktop, iOS, MacOS, and Web (Kotlin/Wasm and Kotlin/JS). These benchmarks measure the performance of various Compose components and features, such as animations, lazy layouts, text rendering, and visual effects.
The benchmarks can be run in different modes, which determine how performance is measured and reported:
SIMPLE: Measures basic frame times without considering VSync. Good for quick checks of raw rendering performance. Enabled by default if no modes are specified.VSYNC_EMULATION: Emulates VSync behavior to estimate missed frames and provide more realistic performance metrics (CPU/GPU percentiles). Enabled by default if no modes are specified.REAL: Runs the benchmark in a real-world scenario with actual VSync. This mode provides the most accurate results for user-perceived performance (FPS, actual missed frames) but may not catch performance regressions if a frame fits to budget. Also requires a device with a real display (may have problems with headless devices).STARTUP: Measures application startup performance. This mode captures detailed timing metrics from process start to the first frame and beyond, including:- Time from process start to the application entry point (
timeToMain) - platform-dependent, available on JVM/Desktop - Time from entry point to the first frame (
timeFromMainToFirstFrame) - Duration of the first frame itself (
timeOfFirstFrame) - Time from the first frame to a configurable Nth frame (
timeToNthFrame, default N=30) - The N longest frames during startup (
longestFrames, default N=3)
- Time from process start to the application entry point (
To enable specific modes, use the modes argument:
modes=SIMPLE,VSYNC_EMULATION,REAL,STARTUP
Multiple modes can be combined. For example, modes=STARTUP,REAL will first measure startup metrics and then run real-time performance measurements, producing a unified report.
You can configure benchmark runs using arguments passed to the Gradle task (via -PrunArguments="...") or to the .main.kts script. Arguments can also be set in gradle.properties using the runArguments property.
| Argument | Description | Example |
|---|---|---|
modes |
Comma-separated list of execution modes (SIMPLE, VSYNC_EMULATION, REAL, STARTUP). |
modes=REAL,STARTUP |
benchmarks |
Comma-separated list of benchmarks to run. Can optionally specify problem size in parentheses. | benchmarks=LazyGrid(100),AnimatedVisibility |
disabledBenchmarks |
Comma-separated list of benchmarks to skip. | disabledBenchmarks=HeavyShader |
warmupCount |
Number of warmup frames before starting measurements. | warmupCount=50 |
frameCount |
Number of frames to measure for each benchmark. | frameCount=500 |
emptyScreenDelay |
Delay in milliseconds between warmup and measurement (real mode only). | emptyScreenDelay=1000 |
startupFrameCount |
Number of frames to measure after the first frame in startup mode (default: 30). | startupFrameCount=50 |
startupLongestFramesCount |
Number of longest frames to report in startup mode (default: 3). | startupLongestFramesCount=5 |
parallel |
(iOS only) Enable parallel rendering. | parallel=true |
saveStatsToCSV |
Save results to CSV files. | saveStatsToCSV=true |
saveStatsToJSON |
Save results to JSON files. | saveStatsToJSON=true |
versionInfo |
Add version information to the report. | versionInfo=1.2.3 |
reportAtTheEnd |
Print a summary report after all benchmarks are finished real mode only). | reportAtTheEnd=true |
listBenchmarks |
List all available benchmarks and exit. | listBenchmarks=true |
./gradlew :benchmarks:run -PrunArguments="benchmarks=LazyGrid modes=REAL frameCount=200"There are several scripts provided to run benchmarks more conveniently, especially when you need to run multiple iterations or compare different versions.
This script is the main entry point for running benchmarks on all supported platforms (macos, desktop, web, ios). It handles platform-specific details like starting a background server for web benchmarks or invoking iOS-specific scripts.
Usage:
./run_benchmarks.main.kts <platform> [runs=1] [benchmarks=<benchmarkName>] [version=<version>] [separateProcess=true|false] [any other gradle args]Platforms: macos, desktop, web, ios
Arguments:
runs=<number>: Number of iterations to run (default: 1).benchmarks=<name1,name2,...>: Filter benchmarks to run.version=<version>: Compose Multiplatform version to use. If specified, the script updatesgradle/libs.versions.tomlbefore running and restores it afterward.separateProcess=true: Runs each benchmark in a separate process for better isolation (default:false).- Any other arguments (e.g.,
modes=SIMPLE) are forwarded to the benchmark execution.
Example:
./run_benchmarks.main.kts macos benchmarks=LazyList runs=3 version=1.10.0Output:
JSON results are archived to: benchmarks/build/benchmarks/archive/${platform}/${version}_run${runIndex}
Used to compare benchmark results between two different versions of Compose Multiplatform.
Usage:
./compare_benchmarks.main.kts v1=<version1> v2=<version2> [runs=3] [platform=macos|desktop|web|ios] [benchmarks=<name>] [skipExisting=true] [separateProcess=true]Example:
./compare_benchmarks.main.kts v1=1.9.0 v2=1.10.0 runs=3 platform=macos skipExisting=truePerforms a binary search over a list of versions to find the first version where a performance degradation (defined as >5% increase in time) was introduced.
Usage:
./find_degradation.main.kts benchmarks=<benchmarkName> versions=<versionsFile> [platform=macos|desktop|web] [skipExisting=true]The versionsFile should be a plain text file with one version per line, sorted from oldest to newest.
./gradlew :benchmarks:run
Open the project in Fleet or Android Studio with KMM plugin installed and
choose iosApp run configuration. Make sure that you build the app in Release configuration.
Alternatively you may open iosApp/iosApp project in XCode and run the app from there.
- To run on device, open
iosApp/iosApp.xcodeprojand properly configure the Signing section on the Signing & Capabilities project tab. - Use the following command to get list of all iOS devices:
xcrun xctrace list devices
- From the benchmarks directory run:
./run_ios_benchmarks.main.kts <DEVICE ID>(supports all modes of running benchmarks, configured the same way as for other targets:
script arguments orrunArgumentsproperty ofgradle.properties)- or
./iosApp/run_ios_benchmarks.sh <DEVICE ID>(shell script supportingrealmode benchmarks running with multiple attempts)
To run specific benchmarks:
./run_ios_benchmarks.main.kts <DEVICE ID> benchmarks=AnimatedVisibility,LazyGrid
To run each benchmark in a separate process (slower but more reliable as some benchmarks may affect others within one process):
./run_ios_benchmarks.main.kts <DEVICE ID> separateProcess=true
- Results are saved in
benchmarks/build/benchmarks/text-reports/(when using.main.kts) orbenchmarks_result/(when using.sh).
./gradlew :benchmarks:runReleaseExecutableMacosArm64(Works on Arm64 processors)./gradlew :benchmarks:runReleaseExecutableMacosX64(Works on Intel processors)
./gradlew :benchmarks:wasmJsD8ProductionRun
or with arguments:
./gradlew :benchmarks:wasmJsD8ProductionRun -PrunArguments=benchmarks=AnimatedVisibility
./gradlew :benchmarks:buildD8Distribution --rerun-tasks
then in a distribution directory run using your D8 binary:
~/.gradle/d8/v8-mac-arm64-rel-11.9.85/d8 --module launcher_jetstream3.mjs -- AnimatedVisibility 1000
Please run your browser with manual GC enabled before running the benchmark, like for Google Chrome:
open -a Google\ Chrome --args --js-flags="--expose-gc"
./gradlew clean :benchmarks:wasmJsBrowserProductionRun(you can see the results printed on the page itself)
./gradlew clean :benchmarks:jsBrowserProductionRun(you can see the results printed on the page itself)
| Benchmark Name | File Path | Description |
|---|---|---|
| AnimatedVisibility | benchmarks/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt | Tests the performance of the AnimatedVisibility component by repeatedly toggling the visibility of a PNG image. |
| LazyGrid | benchmarks/src/commonMain/kotlin/benchmarks/lazygrid/LazyGrid.kt | Tests the performance of the LazyVerticalGrid component with 12,000 items and jumps to specific items multiple times while running. |
| LazyGrid-ItemLaunchedEffect | benchmarks/src/commonMain/kotlin/benchmarks/lazygrid/LazyGrid.kt | Same as LazyGrid but adds a LaunchedEffect in each grid item that simulates an async task. |
| LazyGrid-SmoothScroll | benchmarks/src/commonMain/kotlin/benchmarks/lazygrid/LazyGrid.kt | Same as LazyGrid but uses smooth scrolling instead of jumping to items. |
| LazyGrid-SmoothScroll-ItemLaunchedEffect | benchmarks/src/commonMain/kotlin/benchmarks/lazygrid/LazyGrid.kt | Combines smooth scrolling with LaunchedEffect in each item. |
| VisualEffects | benchmarks/src/commonMain/kotlin/benchmarks/visualeffects/HappyNY.kt | Tests the performance of complex animations and visual effects including snow flakes, stars, and rocket particles. |
| LazyList | benchmarks/src/commonMain/kotlin/benchmarks/complexlazylist/components/MainUI.kt | Tests the performance of a complex LazyColumn implementation with features like pull-to-refresh, loading more items, and continuous scrolling. |
| MultipleComponents | benchmarks/src/commonMain/kotlin/benchmarks/example1/Example1.kt | Tests the performance of a comprehensive UI that showcases various Compose components including layouts, animations, and styled text. |
| MultipleComponents-NoVectorGraphics | benchmarks/src/commonMain/kotlin/benchmarks/example1/Example1.kt | Same as MultipleComponents but skips the Composables with vector graphics rendering. |
| TextLayout | benchmarks/src/commonMain/kotlin/benchmarks/textlayout/TextLayout.kt | Tests text layout and rendering performance by continuously scrolling column with big number of heady to layout items. |
| CanvasDrawing | benchmarks/src/commonMain/kotlin/benchmarks/canvasdrawing/CanvasDrawing.kt | Tests Canvas drawing performance by scrolling items with massive amount of graphic shapes. |
| HeavyShader | benchmarks/src/commonMain/kotlin/benchmarks/heavyshader/HeavyShader.kt | Tests GPU shader performance by scrolling items with a complex GPU shader. |