Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
38 changes: 21 additions & 17 deletions benchmarks/multiplatform/benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import kotlin.text.replace

plugins {
kotlin("multiplatform")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.compose")
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinSerialization)
}

version = "1.0-SNAPSHOT"
Expand Down Expand Up @@ -56,15 +57,16 @@ kotlin {
implementation(compose.material)
implementation(compose.runtime)
implementation(compose.components.resources)
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.2")
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.io)
implementation(libs.kotlinx.datetime)
}
}

val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1")
runtimeOnly(libs.kotlinx.coroutines.swing)
}
}
}
Expand All @@ -77,21 +79,23 @@ compose.desktop {
}

val runArguments: String? by project
val composeVersion: String? = project.properties["compose.version"] as? String
val kotlinVersion: String? = project.properties["kotlin.version"] as? String
var appArgs = runArguments
?.split(" ")
.orEmpty().let {
it + listOf("versionInfo=\"$composeVersion (Kotlin $kotlinVersion)\"")
}
.map {
it.replace(" ", "%20")
}

println("runArguments: $appArgs")
val composeVersion = libs.versions.compose.multiplatform
val kotlinVersion = libs.versions.kotlin

// Handle runArguments property
gradle.taskGraph.whenReady {
var appArgs = runArguments
?.split(" ")
.orEmpty().let {
it + listOf("versionInfo=\"${composeVersion.get()} (Kotlin ${kotlinVersion.get()})\"")
}
.map {
it.replace(" ", "%20")
}

println("runArguments: $appArgs")

tasks.named<JavaExec>("run") {
args(appArgs)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ object Args {
var versionInfo: String? = null
private set

var saveStatsOnDisk: Boolean = false
var saveStatsToCSV: Boolean = false
private set

var saveStatsToJSON: Boolean = false
private set

private fun argToSet(arg: String): Set<String> = arg.substring(arg.indexOf('=') + 1)
Expand Down Expand Up @@ -42,8 +45,10 @@ object Args {
benchmarks += argToMap(arg.decodeArg())
} else if (arg.startsWith("versionInfo=", ignoreCase = true)) {
versionInfo = arg.substringAfter("=").decodeArg()
} else if (arg.startsWith("saveStatsOnDisk=", ignoreCase = true)) {
saveStatsOnDisk = arg.substringAfter("=").toBoolean()
} else if (arg.startsWith("saveStatsToCSV=", ignoreCase = true)) {
saveStatsToCSV = arg.substringAfter("=").toBoolean()
} else if (arg.startsWith("saveStatsToJSON=", ignoreCase = true)) {
saveStatsToJSON = arg.substringAfter("=").toBoolean()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import benchmarks.complexlazylist.components.MainUiNoImageUseModel
import benchmarks.example1.Example1
import benchmarks.lazygrid.LazyGrid
import benchmarks.visualeffects.NYContent
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.math.roundToInt
import kotlin.time.Duration

Expand All @@ -29,6 +31,7 @@ data class BenchmarkFrame(
}
}

@Serializable
data class BenchmarkConditions(
val frameCount: Int,
val warmupCount: Int
Expand All @@ -42,6 +45,7 @@ data class BenchmarkConditions(
}
}

@Serializable
data class FrameInfo(
val cpuTime: Duration,
val gpuTime: Duration,
Expand All @@ -61,11 +65,13 @@ data class FrameInfo(
}
}

@Serializable
data class BenchmarkPercentileAverage(
val percentile: Double,
val average: Duration
)

@Serializable
data class MissedFrames(
val count: Int,
val ratio: Double
Expand All @@ -86,7 +92,11 @@ data class MissedFrames(
}
}

private val json = Json { prettyPrint = true }

@Serializable
data class BenchmarkStats(
val name: String,
val frameBudget: Duration,
val conditions: BenchmarkConditions,
val averageFrameInfo: FrameInfo?,
Expand Down Expand Up @@ -152,9 +162,12 @@ data class BenchmarkStats(
)
}
}

fun toJsonString(): String = json.encodeToString(serializer(), this)
}

class BenchmarkResult(
private val name: String,
private val frameBudget: Duration,
private val conditions: BenchmarkConditions,
private val averageFrameInfo: FrameInfo,
Expand Down Expand Up @@ -182,6 +195,7 @@ class BenchmarkResult(
}

return BenchmarkStats(
name,
frameBudget,
conditions,
averageFrameInfo,
Expand Down Expand Up @@ -221,11 +235,18 @@ suspend fun runBenchmark(
) {
if (Args.isBenchmarkEnabled(name)) {
println("# $name")
val stats = measureComposable(warmupCount, Args.getBenchmarkProblemSize(name, frameCount), width, height, targetFps, graphicsContext, content).generateStats()
val stats = measureComposable(
name,
warmupCount,
Args.getBenchmarkProblemSize(name, frameCount),
width,
height,
targetFps,
graphicsContext,
content
).generateStats()
stats.prettyPrint()
if (Args.saveStatsOnDisk) {
saveBenchmarkStatsOnDisk(name, stats)
}
saveBenchmarkStatsOnDisk(name, stats)
}
}

Expand All @@ -241,6 +262,6 @@ suspend fun runBenchmarks(
runBenchmark("AnimatedVisibility", width, height, targetFps, 1000, graphicsContext) { AnimatedVisibility() }
runBenchmark("LazyGrid", width, height, targetFps, 1000, graphicsContext) { LazyGrid() }
runBenchmark("VisualEffects", width, height, targetFps, 1000, graphicsContext) { NYContent(width, height) }
runBenchmark("LazyList", width, height, targetFps, 1000, graphicsContext) { MainUiNoImageUseModel()}
runBenchmark("LazyList", width, height, targetFps, 1000, graphicsContext) { MainUiNoImageUseModel() }
runBenchmark("Example1", width, height, targetFps, 1000, graphicsContext) { Example1() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,35 @@ import kotlinx.io.readByteArray

fun saveBenchmarkStatsOnDisk(name: String, stats: BenchmarkStats) {
try {
val path = Path("build/benchmarks/$name.csv")
if (Args.saveStatsToCSV) {
val path = Path("build/benchmarks/$name.csv")

val keyToValue = mutableMapOf<String, String>()
keyToValue.put("Date", currentFormattedDate)
stats.putFormattedValuesTo(keyToValue)
val keyToValue = mutableMapOf<String, String>()
keyToValue.put("Date", currentFormattedDate)
stats.putFormattedValuesTo(keyToValue)

var text = if (SystemFileSystem.exists(path)) {
SystemFileSystem.source(path).readText()
} else {
keyToValue.keys.joinToString(",") + "\n"
}
var text = if (SystemFileSystem.exists(path)) {
SystemFileSystem.source(path).readText()
} else {
keyToValue.keys.joinToString(",") + "\n"
}

fun escapeForCSV(value: String) = value.replace(",", ";")
text += keyToValue.values.joinToString(",", transform = ::escapeForCSV) + "\n"

fun escapeForCSV(value: String) = value.replace(",", ";")
text += keyToValue.values.joinToString(",", transform = ::escapeForCSV) + "\n"
SystemFileSystem.createDirectories(path.parent!!)
SystemFileSystem.sink(path).writeText(text)
println("CSV results saved to ${SystemFileSystem.resolve(path)}")
println()
} else if (Args.saveStatsToJSON) {
val jsonString = stats.toJsonString()
val jsonPath = Path("build/benchmarks/json-reports/$name.json")

SystemFileSystem.createDirectories(path.parent!!)
SystemFileSystem.sink(path).writeText(text)
println("Results saved to ${SystemFileSystem.resolve(path)}")
println()
SystemFileSystem.createDirectories(jsonPath.parent!!)
SystemFileSystem.sink(jsonPath).writeText(jsonString)
println("JSON results saved to ${SystemFileSystem.resolve(jsonPath)}")
println()
}
} catch (_: IOException) {
// IOException "Read-only file system" is thrown on iOS without writing permissions
} catch (_: UnsupportedOperationException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ suspend inline fun preciseDelay(duration: Duration) {

@OptIn(ExperimentalTime::class, InternalComposeUiApi::class)
suspend fun measureComposable(
name: String,
warmupCount: Int,
frameCount: Int,
width: Int,
Expand Down Expand Up @@ -121,6 +122,7 @@ suspend fun measureComposable(
}

return BenchmarkResult(
name,
nanosPerFrame.nanoseconds,
BenchmarkConditions(frameCount, warmupCount),
FrameInfo(cpuTotalTime / frameCount, gpuTotalTime / frameCount),
Expand Down
7 changes: 4 additions & 3 deletions benchmarks/multiplatform/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
kotlin("multiplatform") apply false
kotlin("plugin.compose") apply false
id("org.jetbrains.compose") apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.kotlinSerialization) apply false
}

allprojects {
Expand Down
4 changes: 1 addition & 3 deletions benchmarks/multiplatform/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
compose.version=1.7.1
kotlin.version=2.1.0
org.gradle.jvmargs=-Xmx3g
kotlin.native.useEmbeddableCompilerJar=true
compose.desktop.verbose=true
android.useAndroidX=true
runArguments=benchmarks= modes=
kotlin.js.webpack.major.version=4
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.resources.multimodule.disable=true
org.jetbrains.compose.resources.multimodule.disable=false
19 changes: 19 additions & 0 deletions benchmarks/multiplatform/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[versions]
compose-multiplatform = "1.8.0-beta01"
kotlin = "2.1.20"
kotlinx-coroutines = "1.8.0"
kotlinx-serialization = "1.8.0"
kotlinx-io = "0.7.0"
kotlinx-datetime = "0.6.2"

[libraries]
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
kotlinx-io = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinx-io" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }

[plugins]
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
24 changes: 16 additions & 8 deletions benchmarks/multiplatform/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,24 @@ pluginManagement {
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
}

plugins {
val kotlinVersion = extra["kotlin.version"] as String
kotlin("multiplatform").version(kotlinVersion)
id("org.jetbrains.kotlin.plugin.compose").version(kotlinVersion)
val composeVersion = extra["compose.version"] as String
id("org.jetbrains.compose").version(composeVersion)
}
}

rootProject.name = "compose-benchmarks"

dependencyResolutionManagement {
versionCatalogs{
create("libs") {
// Override Kotlin and Compose versions with properties
providers.run {
with(gradleProperty("kotlin.version")) {
if (isPresent) version("kotlin", get())
}
with(gradleProperty("compose.version")) {
if (isPresent) version("compose-multiplatform", get())
}
}
}
}
}

include(":benchmarks")