compose-multiplatform
compose-multiplatform copied to clipboard
Research performance issues on Desktop
According to this article and performed tests with different versions of Compose, performance regression has been noticed after Compose 1.0 and Compose 1.2 (tested on Windows). This is a potential point for further optimization.
Windows 11, Corretto 18, 10000 squares
Compose, Kotlin | FPS |
---|---|
1.5.0, 1.9.0 | 69 |
1.5.0, 1.7.10 | 69 |
1.4.3, 1.7.10 | 68 |
1.3.1, 1.7.10 | 78 |
1.2.1, 1.7.10 | 78 |
1.1.1, 1.6.10 | 122 |
1.0.0, 1.5.31 | 122 |
0.4.0, 1.5.10 | 149 |
A reduced benchmark:
import androidx.compose.ui.awt.ComposeWindow // for >= 1.0.0
// import androidx.compose.desktop.ComposeWindow // for 0.4.0
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import java.awt.Dimension
import javax.swing.SwingUtilities
import kotlin.math.roundToInt
fun main() = SwingUtilities.invokeLater {
System.setProperty("skiko.renderApi", "OPENGL")
System.setProperty("skiko.vsync.enabled", "false")
ComposeWindow().apply {
size = Dimension(1280, 720)
setContent {
App()
}
isVisible = true
}
}
val particlesCount = 10_000
val fpsCounter = FPSCounter()
var time by mutableStateOf(0L)
@Composable
fun App() = Canvas(Modifier.fillMaxSize()) {
drawIntoCanvas {
time++
fpsCounter.tick()
drawRect(Color.Red)
repeat(particlesCount) {
val x = Math.random().toFloat()
val y = Math.random().toFloat()
drawRect(
color = Color.White,
Offset(size.width * x, size.height * y),
Size(10f, 10f)
)
}
}
}
// a reduced copy from skiko
class FPSCounter(
private val periodSeconds: Double = 2.0,
) {
private val times = mutableListOf<Long>()
private var lastLogTime = System.nanoTime()
private var lastTime = System.nanoTime()
fun tick() {
val time = System.nanoTime()
val frameTime = time - lastTime
lastTime = time
times.add(frameTime)
if ((time - lastLogTime) > periodSeconds.secondsToNanos() && times.isNotEmpty()) {
val average = (nanosPerSecond / times.average()).roundToInt()
times.clear()
lastLogTime = time
println("FPS $average")
}
}
private val nanosPerSecond = 1_000_000_000.0
private fun Double.secondsToNanos(): Long = (this * nanosPerSecond).toLong()
}
The regression is in Skiko or in Skia, not in Compose itself. A pure Skiko benchmark shows similar regressions:
// for 0.4.0
//import org.jetbrains.skija.Canvas
//import org.jetbrains.skija.Color
//import org.jetbrains.skija.Paint
//import org.jetbrains.skija.Rect
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color
import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkiaLayer
import java.awt.Dimension
import javax.swing.JFrame
import javax.swing.SwingUtilities
import kotlin.math.roundToInt
val redPaint = Paint().apply {
color = Color.makeRGB(255, 0, 0)
}
val whitePaint = Paint().apply {
color = Color.makeRGB(255, 255, 255)
}
fun main() = SwingUtilities.invokeLater {
System.setProperty("skiko.renderApi", "OPENGL")
System.setProperty("skiko.vsync.enabled", "false")
JFrame().apply {
size = Dimension(1280, 720)
add(SkiaLayer().apply {
size = Dimension(1280, 720)
// for 0.4.0
// renderer = object : org.jetbrains.skiko.SkiaRenderer {
skikoView = object : SkikoView {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
fpsCounter.tick()
canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), redPaint)
repeat(particlesCount) {
val x = Math.random().toFloat()
val y = Math.random().toFloat()
canvas.drawRect(
Rect.makeXYWH(width * x, height * y, 10f, 10f),
whitePaint
)
}
needRedraw()
}
}
})
isVisible = true
}
}
val particlesCount = 10_000
val fpsCounter = FPSCounter()
// a reduced copy from skiko
class FPSCounter(
private val periodSeconds: Double = 2.0,
) {
private val times = mutableListOf<Long>()
private var lastLogTime = System.nanoTime()
private var lastTime = System.nanoTime()
fun tick() {
val time = System.nanoTime()
val frameTime = time - lastTime
lastTime = time
times.add(frameTime)
if ((time - lastLogTime) > periodSeconds.secondsToNanos() && times.isNotEmpty()) {
val average = (nanosPerSecond / times.average()).roundToInt()
times.clear()
lastLogTime = time
println("FPS $average")
}
}
private val nanosPerSecond = 1_000_000_000.0
private fun Double.secondsToNanos(): Long = (this * nanosPerSecond).toLong()
}
Does this mean that it could have some effects on other targets as well? iOS? If so, fixing this problem is going to boost performance for all targets. Also, I think that the title and labels should be changed.
If it is not a Java/JNI overhead issue, then, yes, it should also affect iOS/web platforms.
https://github.com/JetBrains/skiko/compare/master...es/regression-rollback Regression was introduced by commit from which this branch starts, this commit shows FPS similar to Compose 1.1.1.
Regression is not present on macOS targets. We will try to update to latest Skia and see if they fixed the issue.
Hey, Any news about this issue?
We plan to update Skia, and if this won't resolve the issue, we'll look further. It either a Skia regression, or a build configuration change.
Awesome, thanks for the great work! I'm working on a drawing app and users are encountering some latency issues, I hope that this will fix it.
Upgrading Skia from m110 to m116 doesn't help.
I checked the new Skiko with this PR - FPS is the same
Hello, is there any news regarding this problem?
Any news about this problem? I'm very worried because my customers of my desktop project have low-end specs and the FPS is about 10~15 that doesn't support OPENGL I spend the last 2 years working on this project since the compose multiplatform was in 1.0 alpha
Any news about this problem?
Any news? Same problem with the version 1.6.0
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.