Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
30 changes: 29 additions & 1 deletion benchmarks/multiplatform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,38 @@ Alternatively you may open `iosApp/iosApp` project in XCode and run the app from
- `./gradlew :benchmarks:runReleaseExecutableMacosArm64` (Works on Arm64 processors)
- `./gradlew :benchmarks:runReleaseExecutableMacosX64` (Works on Intel processors)

## Run K/Wasm target in D8:
`./gradlew :benchmarks:wasmJsD8ProductionRun`

or with arguments:

`./gradlew :benchmarks:wasmJsD8ProductionRun -PrunArguments=benchmarks=AnimatedVisibility`

## To build and run a K/Wasm D8 distribution for Jetstream3-like:
`./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`

## Run in web browser:

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 :benchmarks:wasmJsBrowserProductionRun` (you can see the results printed on the page itself)
- `./gradlew clean :benchmarks:wasmJsBrowserProductionRun` (you can see the results printed on the page itself)


# Benchmarks description

| Benchmark Name | File Path | Description |
|----------------|-----------|-------------|
| AnimatedVisibility | [benchmarks/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt](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](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](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](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](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](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](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. |
| Example1 | [benchmarks/src/commonMain/kotlin/benchmarks/example1/Example1.kt](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. |
50 changes: 49 additions & 1 deletion benchmarks/multiplatform/benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenRootEnvSpec
import org.jetbrains.kotlin.gradle.targets.js.d8.D8Exec
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import kotlin.text.replace

Expand Down Expand Up @@ -46,7 +48,14 @@ kotlin {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
binaries.executable()
browser ()
d8 {
compilerOptions.freeCompilerArgs.add("-Xwasm-attach-js-exception")
runTask {
// It aborts even on coroutine cancellation exceptions:
// d8Args.add("--abort-on-uncaught-exception")
}
}
browser()
}

sourceSets {
Expand Down Expand Up @@ -113,4 +122,43 @@ gradle.taskGraph.whenReady {
open = "http://localhost:8080?$args"
)
}

@OptIn(ExperimentalWasmDsl::class)
tasks.withType<D8Exec>().configureEach {
inputFileProperty.set(rootProject.layout.buildDirectory.file(
"js/packages/compose-benchmarks-benchmarks-wasm-js/kotlin/launcher.mjs")
)

args(appArgs)
}
}


tasks.register("buildD8Distribution", Zip::class.java) {
dependsOn("wasmJsProductionExecutableCompileSync")
from(rootProject.layout.buildDirectory.file("js/packages/compose-benchmarks-benchmarks-wasm-js/kotlin"))
archiveFileName.set("d8-distribution.zip")
destinationDirectory.set(rootProject.layout.buildDirectory.dir("distributions"))
}

tasks.withType<org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenExec>().configureEach {
binaryenArgs.add("-g") // keep the readable names
}

@OptIn(ExperimentalWasmDsl::class)
rootProject.the<BinaryenRootEnvSpec>().apply {
// version = "122" // change only if needed
}

val jsOrWasmRegex = Regex("js|wasm")

configurations.all {
resolutionStrategy.eachDependency {
if (requested.group.startsWith("org.jetbrains.skiko") &&
jsOrWasmRegex.containsMatchIn(requested.name)
) {
// to keep the readable names from Skiko
useVersion(requested.version!! + "+profiling")
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ object Args {

private val benchmarks = mutableMapOf<String, Int>()

private val benchmarksToSkip = mutableSetOf<String>()

var versionInfo: String? = null
private set

Expand Down Expand Up @@ -38,6 +40,8 @@ object Args {
* with values separated by commas.
*/
fun parseArgs(args: Array<String>) {
// reset the previous configuration before setting a new one for cases when parseArgs is called more than once:
reset()
for (arg in args) {
if (arg.startsWith("modes=", ignoreCase = true)) {
modes.addAll(argToSet(arg.decodeArg()).map { Mode.valueOf(it) })
Expand All @@ -55,12 +59,28 @@ object Args {

private fun String.decodeArg() = replace("%20", " ")

private fun reset() {
modes.clear()
benchmarks.clear()
}

fun enableModes(vararg modes: Mode) = this.modes.addAll(modes)

fun isModeEnabled(mode: Mode): Boolean = modes.isEmpty() || modes.contains(mode)

fun isBenchmarkEnabled(benchmark: String): Boolean = benchmarks.isEmpty() || benchmarks.contains(benchmark.uppercase())
fun isBenchmarkEnabled(benchmark: String): Boolean {
return (benchmarks.isEmpty() || benchmarks.contains(benchmark.uppercase()))
&& !benchmarksToSkip.contains(benchmark.uppercase())
}

fun getBenchmarkProblemSize(benchmark: String, default: Int): Int {
val result = benchmarks[benchmark.uppercase()]?: -1
return if (result == -1) default else result
}

// Some targets can't support all the benchmarks (e.g. D8) due to limited APIs availability,
// so we skip them
fun skipBenchmark(benchmark: String) {
benchmarksToSkip.add(benchmark.uppercase())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,24 @@ suspend fun runBenchmarks(
width: Int = 1920,
height: Int = 1080,
targetFps: Int = 120,
warmupCount: Int = 100,
graphicsContext: GraphicsContext? = null
) {
println()
println("Running emulating $targetFps FPS")
println()
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("Example1", width, height, targetFps, 1000, graphicsContext) { Example1() }
runBenchmark("AnimatedVisibility", width, height, targetFps, 1000, graphicsContext, warmupCount) { AnimatedVisibility() }
runBenchmark("LazyGrid", width, height, targetFps, 1000, graphicsContext, warmupCount) { LazyGrid() }
runBenchmark("LazyGrid-ItemLaunchedEffect", width, height, targetFps, 1000, graphicsContext, warmupCount) {
LazyGrid(smoothScroll = false, withLaunchedEffectInItem = true)
}
runBenchmark("LazyGrid-SmoothScroll", width, height, targetFps, 1000, graphicsContext, warmupCount) {
LazyGrid(smoothScroll = true)
}
runBenchmark("LazyGrid-SmoothScroll-ItemLaunchedEffect", width, height, targetFps, 1000, graphicsContext, warmupCount) {
LazyGrid(smoothScroll = true, withLaunchedEffectInItem = true)
}
runBenchmark("VisualEffects", width, height, targetFps, 1000, graphicsContext, warmupCount) { NYContent(width, height) }
runBenchmark("LazyList", width, height, targetFps, 1000, graphicsContext, warmupCount) { MainUiNoImageUseModel()}
runBenchmark("Example1", width, height, targetFps, 1000, graphicsContext, warmupCount) { Example1() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ suspend inline fun preciseDelay(duration: Duration) {
while (liveDelayStart.elapsedNow() < liveDelay){}
}

/**
* Some of the benchmarks involved an asynchronous fetch operation for resources when running in a browser.
* To let the fetch operation result be handled by compose, the benchmark loop must yield the event loop.
* Otherwise, such benchmark do not run a part of workload, making the stats less meaningful.
*
* It makes sense for all platforms, since there could be some work scheduled to run on the same Thread
* as the benchmarks runner. But without yielding, it won't be dispatched.
*/
private suspend inline fun yieldEventLoop() {
yield()
}

@OptIn(ExperimentalTime::class, InternalComposeUiApi::class)
suspend fun measureComposable(
name: String,
Expand All @@ -62,6 +74,7 @@ suspend fun measureComposable(
scene.mimicSkikoRender(surface, it * nanosPerFrame, width, height)
surface.flushAndSubmit(false)
graphicsContext?.awaitGPUCompletion()
yieldEventLoop()
}

runGC()
Expand All @@ -76,6 +89,7 @@ suspend fun measureComposable(
gpuTotalTime += measureTime {
graphicsContext?.awaitGPUCompletion()
}
yieldEventLoop()
}
}
cpuTotalTime -= gpuTotalTime
Expand Down Expand Up @@ -118,6 +132,8 @@ suspend fun measureComposable(
// Emulate waiting for next vsync
preciseDelay(timeUntilNextVSync)
}

yieldEventLoop()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import compose_benchmarks.benchmarks.generated.resources.Res
import compose_benchmarks.benchmarks.generated.resources.compose_multiplatform
import compose_benchmarks.benchmarks.generated.resources.img
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource

Expand All @@ -38,7 +38,7 @@ fun AnimatedVisibility() {
AnimatedVisibility(showImage) {
transition = this.transition
Image(
painterResource(Res.drawable.compose_multiplatform),
painterResource(Res.drawable.img),
null
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,25 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import kotlin.coroutines.suspendCoroutine

@Composable
fun LazyGrid() {
fun LazyGrid(smoothScroll: Boolean = false, withLaunchedEffectInItem: Boolean = false) {
val itemCount = 12000
val entries = remember {List(itemCount) { Entry("$it") }}
val state = rememberLazyGridState()

var smoothScroll by remember { mutableStateOf(false)}
var scrollIteration by remember { mutableStateOf(0) }

MaterialTheme {
Column {
Row {
Checkbox(
checked = smoothScroll,
onCheckedChange = { value -> smoothScroll = value}
)
Text (text = "Smooth scroll", modifier = Modifier.align(Alignment.CenterVertically))
}

LazyVerticalGrid(
columns = GridCells.Fixed(4),
modifier = Modifier.fillMaxWidth().semantics { contentDescription = "IamLazy" },
state = state
) {
items(entries) {
ListCell(it)
ListCell(it, withLaunchedEffectInItem)
}
}
}
Expand All @@ -67,12 +60,12 @@ fun LazyGrid() {
curItem = state.firstVisibleItemIndex
if (curItem == 0) direct = true
if (curItem > itemCount - 100) direct = false
state.scrollBy(if (direct) 5f else -5f)
state.scrollBy(if (direct) 55f else -55f)
}
}
} else {
LaunchedEffect(curItem) {
withFrameMillis { }
LaunchedEffect(scrollIteration) {
withFrameMillis {}
curItem += if (direct) 50 else -50
if (curItem >= itemCount) {
direct = false
Expand All @@ -82,15 +75,15 @@ fun LazyGrid() {
curItem = 0
}
state.scrollToItem(curItem)
scrollIteration += 1
}
}

}

data class Entry(val contents: String)

@Composable
private fun ListCell(entry: Entry) {
private fun ListCell(entry: Entry, withLaunchedEffect: Boolean = false) {
Card(
modifier = Modifier
.fillMaxWidth()
Expand All @@ -102,5 +95,12 @@ private fun ListCell(entry: Entry) {
style = MaterialTheme.typography.h5,
modifier = Modifier.padding(16.dp)
)

if (withLaunchedEffect) {
LaunchedEffect(Unit) {
// Never resumed to imitate some async task running in an item's scope
suspendCoroutine { }
}
}
}
}
Loading
Loading