Browse Source

Benchmarks refactoring (#5115)

Fixes [the
issue](https://youtrack.jetbrains.com/issue/CMP-5894/Refactor-Compose-benchmarks-to-count-GC-between-frames):
count GC between frames.

## Testing

Run the benchmarks locally.
pull/5116/head
Nikita Lipsky 5 months ago committed by GitHub
parent
commit
d30da0ab11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 13
      benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/kotlin/MeasureComposable.kt
  2. 3
      benchmarks/kn-performance/src/commonMain/kotlin/Benchmarks.kt
  3. 83
      benchmarks/kn-performance/src/commonMain/kotlin/MeasureComposable.kt
  4. 3
      benchmarks/kn-performance/src/desktopMain/kotlin/runGC.jvm.kt
  5. 7
      benchmarks/kn-performance/src/macosMain/kotlin/runGC.native.kt

13
benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/kotlin/MeasureComposable.kt

@ -1,6 +1,9 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.ComposeScene
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.graphics.asComposeCanvas
import androidx.compose.ui.scene.MultiLayerComposeScene
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
@ -10,20 +13,20 @@ const val height = 480
const val nanosPerSecond = 1E9.toLong()
const val nanosPerFrame = (0.16 * nanosPerSecond).toLong()
@OptIn(ExperimentalTime::class)
@OptIn(ExperimentalTime::class, InternalComposeUiApi::class)
fun measureComposable(
frameCount: Int = 1000,
content: @Composable () -> Unit
): Duration {
val scene = ComposeScene()
val scene = MultiLayerComposeScene(size = IntSize(width, height))
try {
scene.setContent(content)
scene.constraints = Constraints.fixed(width, height)
val surface = org.jetbrains.skia.Surface.makeNull(width, height)
val canvas = surface.canvas.asComposeCanvas()
return kotlin.time.measureTime {
var nanoTime = 0L
repeat(frameCount) {
scene.render(surface.canvas, nanoTime)
scene.render(canvas, nanoTime)
nanoTime += nanosPerFrame
}
}

3
benchmarks/kn-performance/src/commonMain/kotlin/Benchmarks.kt

@ -127,9 +127,10 @@ fun runBenchmark(
targetFps: Int,
frameCount: Int,
graphicsContext: GraphicsContext?,
warmupCount: Int = 100,
content: @Composable () -> Unit
): BenchmarkStats {
val stats = measureComposable(frameCount, width, height, targetFps, graphicsContext, content).generateStats()
val stats = measureComposable(warmupCount, frameCount, width, height, targetFps, graphicsContext, content).generateStats()
println(name)
stats.prettyPrint()

83
benchmarks/kn-performance/src/commonMain/kotlin/MeasureComposable.kt

@ -1,17 +1,17 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.ComposeScene
import androidx.compose.ui.unit.Constraints
import org.jetbrains.skia.DirectContext
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.graphics.asComposeCanvas
import androidx.compose.ui.scene.MultiLayerComposeScene
import androidx.compose.ui.unit.IntSize
import org.jetbrains.skia.Surface
import kotlin.time.Duration
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.TimeSource.Monotonic.markNow
const val nanosPerSecond = 1E9.toLong()
const val millisPerSecond = 1e3.toLong()
const val nanosPerMillisecond = nanosPerSecond / millisPerSecond
interface GraphicsContext {
fun surface(width: Int, height: Int): Surface
@ -19,51 +19,78 @@ interface GraphicsContext {
suspend fun awaitGPUCompletion()
}
@OptIn(ExperimentalTime::class)
expect fun runGC()
suspend inline fun preciseDelay(duration: Duration) {
val liveDelay: Duration
if (duration.inWholeMilliseconds > 1) {
val delayMillis = duration.inWholeMilliseconds - 1
delay(delayMillis)
liveDelay = duration - delayMillis.milliseconds
} else {
liveDelay = duration
}
val start = markNow()
while (start.elapsedNow() < liveDelay){}
}
@OptIn(ExperimentalTime::class, InternalComposeUiApi::class)
fun measureComposable(
frameCount: Int = 500,
warmupCount: Int,
frameCount: Int,
width: Int,
height: Int,
targetFps: Int,
graphicsContext: GraphicsContext?,
content: @Composable () -> Unit
): BenchmarkResult = runBlocking {
val scene = ComposeScene()
val scene = MultiLayerComposeScene(size = IntSize(width, height))
try {
val nanosPerFrame = (1.0 / targetFps.toDouble() * nanosPerSecond).toLong()
scene.setContent(content)
scene.constraints = Constraints.fixed(width, height)
val surface = graphicsContext?.surface(width, height) ?: Surface.makeNull(width, height)
val canvas = surface.canvas.asComposeCanvas()
// warmup
repeat(warmupCount) {
scene.render(canvas, it * nanosPerFrame)
}
val frames = MutableList(frameCount) {
BenchmarkFrame(Duration.INFINITE, Duration.INFINITE)
}
var nanoTime = 0L
var nextVSync = Duration.ZERO
var missedFrames = 0;
runGC()
val start = markNow()
repeat(frameCount) {
val frameTime = measureTime {
val cpuTime = measureTime {
scene.render(surface.canvas, nanoTime)
surface.flushAndSubmit(false)
}
val frameStart = start + nextVSync
val gpuTime = measureTime {
graphicsContext?.awaitGPUCompletion()
}
scene.render(canvas, nextVSync.inWholeNanoseconds)
surface.flushAndSubmit(false)
frames[it] = BenchmarkFrame(cpuTime, gpuTime)
}
val cpuTime = frameStart.elapsedNow()
val actualNanosPerFrame = frameTime.inWholeNanoseconds
val nanosUntilDeadline = nanosPerFrame - actualNanosPerFrame
graphicsContext?.awaitGPUCompletion()
// Emulate waiting for next vsync
if (nanosUntilDeadline > 0) {
delay(nanosUntilDeadline / nanosPerMillisecond)
}
val frameTime = frameStart.elapsedNow()
frames[it] = BenchmarkFrame(cpuTime, frameTime - cpuTime)
missedFrames += (frameTime.inWholeNanoseconds / nanosPerFrame).toInt()
nanoTime += maxOf(actualNanosPerFrame, nanosPerFrame)
nextVSync = ((it + 1 + missedFrames) * nanosPerFrame).nanoseconds
val timeUntilNextVSync = nextVSync - start.elapsedNow()
if (timeUntilNextVSync > Duration.ZERO) {
// Emulate waiting for next vsync
preciseDelay(timeUntilNextVSync)
}
}
BenchmarkResult(

3
benchmarks/kn-performance/src/desktopMain/kotlin/runGC.jvm.kt

@ -0,0 +1,3 @@
actual fun runGC() {
System.gc()
}

7
benchmarks/kn-performance/src/macosMain/kotlin/runGC.native.kt

@ -0,0 +1,7 @@
import kotlin.native.runtime.GC
import kotlin.native.runtime.NativeRuntimeApi
@OptIn(NativeRuntimeApi::class)
actual fun runGC() {
GC.collect()
}
Loading…
Cancel
Save