Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions benchmarks/multiplatform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,32 @@ The benchmarks can be run in different modes, which determine how performance is
- **`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)

To enable specific modes, use the `modes` argument:
`modes=SIMPLE,VSYNC_EMULATION,REAL`
`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.

## Configuration Arguments

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`). | `modes=REAL` |
| `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` |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import kotlin.time.TimeSource
import kotlinx.cinterop.*
import platform.posix.*
import platform.darwin.*
import kotlin.time.Duration.Companion.nanoseconds

@OptIn(ExperimentalForeignApi::class)
actual fun getProcessStartTime(): TimeSource.Monotonic.ValueTimeMark? {
memScoped {
val mib = allocArray<IntVar>(4)
mib[0] = CTL_KERN
mib[1] = KERN_PROC
mib[2] = KERN_PROC_PID
mib[3] = getpid()

val size = alloc<size_tVar>()
size.value = sizeOf<kinfo_proc>().convert()
val info = alloc<kinfo_proc>()

if (sysctl(mib, 4u, info.ptr, size.ptr, null, 0uL) != 0) {
return null
}

// extern_proc.p_un (a union) is the first field of extern_proc, which is the first field of
// kinfo_proc. The union member __p_starttime (timeval) starts at offset 0 within the union.
// Therefore timeval is at offset 0 of kinfo_proc, and we can read it directly via reinterpret.
// Kotlin/Native doesn't expose extern_proc's inner fields, so we use this layout-based access.
val startTime = info.ptr.reinterpret<timeval>().pointed

val now = alloc<timeval>()
gettimeofday(now.ptr, null)

val elapsedNs = (now.tv_sec - startTime.tv_sec) * 1_000_000_000L +
(now.tv_usec - startTime.tv_usec) * 1_000L

return TimeSource.Monotonic.markNow() - elapsedNs.nanoseconds
}
}
Loading
Loading