Compose Multiplatform 跨平台性能基准测试指南:模式、参数与运行脚本全解析
【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform
本篇技术指南围绕 Compose Multiplatform 官方仓库中的 benchmarks/multiplatform 基准测试项目展开,系统讲解其在 Desktop、iOS、macOS、Web(Kotlin/Wasm 与 Kotlin/JS)等目标平台上的性能测量机制、四种执行模式(SIMPLE、VSYNC_EMULATION、REAL、STARTUP)的差异与适用场景、完整的配置参数体系,以及三个.main.kts自动化脚本(run_benchmarks、compare_benchmarks、find_degradation)的实战用法。读者读完后,将能够独立配置并运行跨平台基准测试、对比不同 Compose Multiplatform 版本之间的性能差异,并依据源码级证据定位性能回退的具体版本。
基准测试项目概览
Compose Multiplatform 基准测试项目位于仓库的 benchmarks/multiplatform 目录,其核心目标是对 Compose Multiplatform 在不同目标平台上的各类组件与特性进行量化性能测量,覆盖范围包括:
- 动画(如
AnimatedVisibility的显隐切换动画) - 懒加载布局(
LazyVerticalGrid、复杂LazyColumn) - 文本渲染与排版(大规模文本项的连续滚动排版)
- 视觉效果(雪花、星星、火箭粒子等复合动画特效)
- GPU 着色器(多层渐变与混合模式的 Canvas 绘制)
项目采用 Gradle 多模块结构(settings.gradle.kts),由:benchmarks主模块与:compose-scene-api、:compose-scene-impl场景实现模块组成。其中:compose-scene-impl会根据当前 Compose Multiplatform 版本自动切换底层场景实现(compose-scene-impl-1/2/3),保证基准测试在不同版本下都能以正确的内部 API 运行,这一点在"版本管理与场景实现适配"一节详述。
所有基准测试代码均位于 benchmarks/src/commonMain/kotlin/benchmarks 下,按功能域分目录组织,且全部使用commonMain共享源码,因此同一份基准测试代码可在所有目标平台上运行。
Benchmark 模式详解
基准测试支持四种执行模式,它们决定了性能的测量方式与报告口径:
SIMPLE(简单模式)
直接测量基础帧耗时,不考虑 VSync 同步。适合快速检查原始渲染性能,例如在持续滚动时粗看帧率是否达标。当未通过modes参数指定任何模式时,SIMPLE 与 VSYNC_EMULATION 会作为默认模式启用。
VSYNC_EMULATION(VSync 仿真模式)
模拟 VSync 行为,估算丢帧情况,并给出更贴近真实体验的 CPU/GPU 百分位指标。它不需要真实显示设备,因此在无头(headless)环境中也能运行,适合 CI 流水线中做回归检测。
REAL(真实模式)
在带真实 VSync 的真实场景中运行,能给出用户可感知的最准确结果(FPS、实际丢帧数)。但存在两个局限:其一,如果某一帧耗时恰好落在预算之内,可能捕捉不到性能回退;其二,要求设备具有真实显示器,在无头设备上可能存在问题。
STARTUP(启动性能模式)
专门测量应用启动性能,从进程启动到首帧乃至后续帧输出一系列细粒度时间指标:
| 指标 | 含义 |
|---|---|
timeToMain | 从进程启动到进入应用入口点的时间,平台相关,仅 JVM/Desktop 可用 |
timeFromMainToFirstFrame | 从入口点到渲染出第一帧的时间 |
timeOfFirstFrame | 第一帧本身的渲染耗时 |
timeToNthFrame | 从第一帧到可配置的第 N 帧的时间,默认 N=30 |
longestFrames | 启动过程中耗时最长的 N 帧,默认 N=3 |
模式组合
通过modes参数可以启用一个或多个模式,模式间以逗号分隔:
modes=SIMPLE,VSYNC_EMULATION,REAL,STARTUP多个模式可以组合执行,例如modes=STARTUP,REAL会先测量启动指标,再运行实时性能测量,最终产出一份统一报告。值得注意的是,从 run_benchmarks.main.kts 的源码可以看到,脚本内部会检测modes参数中是否包含startup,若包含则会自动将separateProcess默认置为true——因为启动性能测量需要进程级隔离才足够准确。
配置参数完整说明
基准测试的配置参数可以通过三种方式传入:
- Gradle 任务参数:
-PrunArguments="..."(推荐方式) .main.kts脚本参数:直接作为脚本命令行参数gradle.properties中的runArguments属性
参数一览表
| 参数 | 说明 | 示例 |
|---|---|---|
modes | 逗号分隔的执行模式列表(SIMPLE、VSYNC_EMULATION、REAL、STARTUP) | modes=REAL,STARTUP |
benchmarks | 逗号分隔的待运行基准测试列表,可在括号中指定问题规模 | benchmarks=LazyGrid(100),AnimatedVisibility |
disabledBenchmarks | 逗号分隔的需要跳过的基准测试列表 | disabledBenchmarks=HeavyShader |
warmupCount | 测量开始前的预热帧数 | warmupCount=50 |
frameCount | 每个基准测试的测量帧数 | frameCount=500 |
emptyScreenDelay | 预热与测量之间的延迟(毫秒),仅 REAL 模式生效 | emptyScreenDelay=1000 |
startupFrameCount | 启动模式下首帧之后要测量的帧数(默认 30) | startupFrameCount=50 |
startupLongestFramesCount | 启动模式下要报告的耗时最长帧数(默认 3) | startupLongestFramesCount=5 |
parallel | 是否启用并行渲染,仅 iOS 生效 | parallel=true |
saveStatsToCSV | 是否将结果保存为 CSV 文件 | saveStatsToCSV=true |
saveStatsToJSON | 是否将结果保存为 JSON 文件 | saveStatsToJSON=true |
versionInfo | 在报告中附加版本信息 | versionInfo=1.2.3 |
reportAtTheEnd | 所有基准测试完成后打印汇总报告,仅 REAL 模式生效 | reportAtTheEnd=true |
listBenchmarks | 列出所有可用基准测试后退出 | listBenchmarks=true |
benchmarks参数支持指定问题规模,例如LazyGrid(100)表示以 100 个条目的规模运行 LazyGrid 基准。这一机制对应源码中不同基准测试的规模参数化设计(详见下文基准测试源码解析)。
命令行用法示例
./gradlew :benchmarks:run -PrunArguments="benchmarks=LazyGrid modes=REAL frameCount=200"该命令在 Desktop 平台上以 REAL 模式、测量 200 帧的配置运行LazyGrid基准测试。
各平台运行方式
Desktop
Desktop 平台是上手最快的目标,直接运行 Gradle 任务即可:
./gradlew :benchmarks:run这是基准测试的默认入口,所有支持的运行参数均可通过-PrunArguments传递。
iOS(原生)
在 iOS 上运行有两种途径:
- 使用 Fleet 或安装了 KMM 插件的 Android Studio 打开项目,选择
iosApp运行配置,务必在 Release 配置下构建应用; - 在 Xcode 中打开
iosApp/iosApp项目并直接运行。
macOS(原生)
根据处理器架构选择对应的 Gradle 任务:
./gradlew :benchmarks:runReleaseExecutableMacosArm64 # Arm64 处理器 ./gradlew :benchmarks:runReleaseExecutableMacosX64 # Intel 处理器Web(Kotlin/Wasm 与 Kotlin/JS)
在浏览器中运行前,建议先以**手动 GC(Manual GC)**模式启动浏览器,以获得更干净的测量环境。以 Google Chrome 为例:
open -a Google\ Chrome --args --js-flags="--expose-gc"- Kotlin/Wasm:
./gradlew clean :benchmarks:wasmJsBrowserProductionRun,结果直接打印在页面本身上; - Kotlin/JS:
./gradlew clean :benchmarks:jsBrowserProductionRun,同样在页面打印结果; - Wasm 的 D8(V8 引擎)目标:
./gradlew :benchmarks:wasmJsD8ProductionRun,可携带参数运行,如./gradlew :benchmarks:wasmJsD8ProductionRun -PrunArguments=benchmarks=AnimatedVisibility。
如需构建 Jetstream3 风格的 Wasm D8 发行包并直接用 V8 二进制运行:
./gradlew :benchmarks:buildD8Distribution --rerun-tasks # 在发行包目录中执行(路径仅为示例) ~/.gradle/d8/v8-mac-arm64-rel-11.9.85/d8 --module launcher_jetstream3.mjs -- AnimatedVisibility 1000查看可用基准测试
运行listBenchmarks=true可以列出项目内全部可用的基准测试名称,脚本层(run_benchmarks.main.kts)正是通过解析AVAILABLE_BENCHMARKS_START与AVAILABLE_BENCHMARKS_END标记行来动态获取该列表,用于separateProcess=true时逐个进程分发执行。
一键运行脚本:run_benchmarks.main.kts
run_benchmarks.main.kts 是跨平台运行基准测试的主入口脚本,统一封装了 macos、desktop、web、ios(源码中还包含 android)四类平台。它处理了大量平台相关细节,例如:为 Web 基准测试启动后台服务器(runServer=true)、为 macOS 将构建产物重命名为带版本号的可执行文件、调用 iOS/Android 专属脚本等。
用法与参数
./run_benchmarks.main.kts <platform> [runs=1] [benchmarks=<benchmarkName>] [version=<version>] [separateProcess=true|false] [any other gradle args]| 参数 | 说明 |
|---|---|
<platform> | 目标平台:macos、desktop、web、ios |
runs=<number> | 迭代次数,默认 1 |
benchmarks=<name1,name2,...> | 过滤要运行的基准测试 |
version=<version> | 指定 Compose Multiplatform 版本;脚本会先改写gradle/libs.versions.toml再运行,结束后自动还原 |
separateProcess=true | 每个基准测试在独立进程中运行,隔离性更好(默认false) |
| 其他参数 | 直接透传给基准测试执行,例如modes=SIMPLE |
示例
./run_benchmarks.main.kts macos benchmarks=LazyList runs=3 version=1.10.0该命令在 macOS 上以1.10.0版本连续运行 3 轮LazyList基准测试。从源码(run_benchmarks.main.kts)可以确认其版本管理逻辑:脚本会在运行前读取并暂存gradle/libs.versions.toml,通过正则替换compose-multiplatform版本号,全部轮次结束后在finally块中恢复原文件内容,避免污染仓库配置。
结果归档
每轮运行结束后,JSON 结果会被归档到如下目录(源码见 run_benchmarks.main.kts):
benchmarks/build/benchmarks/archive/${platform}/${version}_run${runIndex}脚本在归档前会先清理benchmarks/build/benchmarks/json-reports目录中上一轮的 JSON 文件,防止旧结果被误归档进新轮次。
版本对比脚本:compare_benchmarks.main.kts
compare_benchmarks.main.kts 用于对比两个 Compose Multiplatform 版本的基准测试结果,是性能回归分析的基础工具。
./compare_benchmarks.main.kts v1=<version1> v2=<version2> [runs=3] [platform=macos|desktop|web|ios] [benchmarks=<name>] [skipExisting=true] [separateProcess=true]示例:
./compare_benchmarks.main.kts v1=1.9.0 v2=1.10.0 runs=3 platform=macos skipExisting=true参数说明:
v1/v2:待对比的两个版本号;runs:每个版本的运行轮数(默认 3);platform:目标平台;benchmarks:仅对比指定基准测试;skipExisting=true:跳过已经归档过结果的版本,节省重复运行时间;separateProcess=true:每个基准测试独立进程运行。
回归定位脚本:find_degradation.main.kts
find_degradation.main.kts 在版本列表上执行二分搜索,定位首个引入性能回退(定义为耗时增幅 >5%)的版本。
./find_degradation.main.kts benchmarks=<benchmarkName> versions=<versionsFile> [platform=macos|desktop|web] [skipExisting=true]其中versionsFile是一个纯文本文件,每行一个版本号,且必须按从旧到新的顺序排列。该脚本的核心价值在于:当发现某一指标从版本 A 到版本 B 明显劣化后,可以自动在 A 与 B 之间的所有历史版本中进行二分查找,精确定位回退由哪个版本引入,为代码评审和 bug 追踪提供明确的范围。
iOS 专属脚本
针对 iOS 平台,仓库提供了两套运行工具:
.main.kts脚本:./run_ios_benchmarks.main.kts <DEVICE ID>,支持与其它目标平台完全一致的参数配置方式(脚本参数或gradle.properties中的runArguments属性);- Shell 脚本:iosApp/run_ios_benchmarks.sh
<DEVICE ID>,专门支持real模式下多次尝试运行的场景。
准备步骤
- 若要在真机运行,先打开
iosApp/iosApp.xcodeproj,在Signing & Capabilities项目页签中正确配置签名(Signing)部分; - 使用
xcrun xctrace list devices获取全部 iOS 设备 ID 列表。
常用命令
# 从 benchmarks 目录运行指定设备上的全部基准测试 ./run_ios_benchmarks.main.kts <DEVICE ID> # 只运行指定的基准测试 ./run_ios_benchmarks.main.kts <DEVICE ID> benchmarks=AnimatedVisibility,LazyGrid # 每个基准测试独立进程运行(较慢但更可靠,避免同进程内相互干扰) ./run_ios_benchmarks.main.kts <DEVICE ID> separateProcess=true结果输出位置
- 使用
.main.kts脚本:结果保存于benchmarks/build/benchmarks/text-reports/; - 使用
.sh脚本:结果保存于benchmarks_result/。
内置基准测试与源码解析
项目内置了 9 组基准测试,全部位于 benchmarks/src/commonMain/kotlin/benchmarks,共享同一份commonMain代码,因此测试场景在任何平台完全一致,保证了跨平台对比的可信度。
| 基准测试名称 | 源码文件 | 测试内容 |
|---|---|---|
| AnimatedVisibility | animation/AnimatedVisibility.kt | 反复切换 PNG 图片的可见性,测试AnimatedVisibility组件性能 |
| LazyGrid | lazygrid/LazyGrid.kt | 12000 条目的LazyVerticalGrid,运行中多次跳转到指定条目 |
| LazyGrid-ItemLaunchedEffect | 同上 | 同 LazyGrid,但每个条目额外带一个模拟异步任务的LaunchedEffect |
| LazyGrid-SmoothScroll | 同上 | 同 LazyGrid,但改用平滑滚动而非跳转 |
| LazyGrid-SmoothScroll-ItemLaunchedEffect | 同上 | 组合了平滑滚动与条目内LaunchedEffect |
| VisualEffects | visualeffects/HappyNY.kt | 复杂动画与视觉特效:雪花、星星、火箭粒子 |
| LazyList | complexlazylist/components/MainUI.kt | 复杂LazyColumn:下拉刷新、加载更多、持续滚动 |
| MultipleComponents | multipleComponents/MultipleComponents.kt | 综合 UI:布局、动画、样式化文本等大量组件同屏展示 |
| MultipleComponents-NoVectorGraphics | 同上 | 同 MultipleComponents,但跳过矢量图形渲染的 Composable |
| TextLayout | textlayout/TextLayout.kt | 连续滚动包含大量重排版条目的列,测试文本布局与渲染性能 |
| CanvasDrawing | canvasdrawing/CanvasDrawing.kt | 滚动包含海量图形形状的条目,测试 Canvas 绘制性能 |
| HeavyShader | heavyshader/HeavyShader.kt | 滚动包含复杂 GPU 着色器的条目,测试 GPU 着色器性能 |
源码级实现细节
LazyGrid 系列(LazyGrid.kt):固定 4 列(GridCells.Fixed(4)),预生成 12000 个条目。跳转模式下,每帧以 50 为步长执行state.scrollToItem(curItem),在列表头尾之间往返;平滑滚动模式下改为每帧state.scrollBy(55f)(或 -55f 反向)。ItemLaunchedEffect变体在每个条目内启动一个LaunchedEffect,其协程体执行suspendCoroutine { }挂起后永不恢复,用于模拟条目作用域中存在长驻异步任务时的开销。
VisualEffects(HappyNY.kt):场景包含 80 片雪花(snowCount)、60 颗星星(starCount)与 30 个火箭粒子(rocketPartsCount)。雪花带有重力、正弦摆动与旋转相位,星星为带旋转的十字形,火箭采用两级爆炸模型——主火箭减速后分裂为 7 枚子火箭,每枚再爆裂为 30 个带拖尾渐隐的粒子,整体以withFrameNanos驱动逐帧状态更新。
TextLayout(TextLayout.kt):共 2000 个条目,每个条目内嵌套 12 行 × 10 列的网格,每个单元格均含小字号Text(6.sp),配合每帧scrollBy(67f)的持续滚动,形成高密度的文本测量、排版与重绘压力。
CanvasDrawing(CanvasDrawing.kt):1200 个条目,每个条目 300.dp 高的 Canvas 内绘制 200 条渐变路径(含quadraticBezierTo贝塞尔曲线)、70 条摆动线条与 70 个脉动圆,全部由无限动画驱动,重点考验 Canvas 图元与渐变渲染吞吐。
HeavyShader(HeavyShader.kt):800 个条目,每个 Canvas 内叠加 60 层径向渐变矩形(layers = 60),逐层使用Screen、Overlay、Multiply三种BlendMode轮换混合,另附 60 个BlendMode.Plus的光晕圆,对 GPU 像素填充率与混合单元构成重度负载。
LazyList(MainUI.kt):复刻真实社交信息流场景,基于SwipeRefreshLayout实现下拉刷新(delay(2000)模拟网络请求后重建数据)与上滑加载更多,同时内部以scrollBy(20f)保持持续滚动,覆盖了含嵌套滚动手势的复杂懒加载列表性能。
MultipleComponents(MultipleComponents.kt):几乎集齐 Material 组件全家桶——TopAppBar、ExtendedFloatingActionButton、BottomAppBar、Switch、Checkbox、Slider、DropdownMenu、TextField、CircularProgressIndicator,以及富文本(buildAnnotatedString+ 内联Placeholder)、BrushTextGradient/BrushTextImage渐变文本、10 万条目的右侧LazyColumn等。MultipleComponents-NoVectorGraphics变体通过isVectorGraphicsSupported = false跳过Image与Icon等矢量资源绘制,用于分离矢量渲染在总开销中的占比。
结果输出与格式
基准测试结果支持三种输出通道,可自由组合:
- JSON(
saveStatsToJSON=true):结构化数据,供脚本归档与版本对比使用,归档路径为benchmarks/build/benchmarks/archive/${platform}/${version}_run${runIndex}; - CSV(
saveStatsToCSV=true):表格化数据,便于在电子表格工具中做进一步分析; - 文本报告:iOS 平台经
.main.kts脚本运行时输出到benchmarks/build/benchmarks/text-reports/,经.sh脚本运行时输出到benchmarks_result/。
版本管理与场景实现适配
从 settings.gradle.kts 的源码可以看出,该基准测试项目对 Compose Multiplatform 版本变化做了精细的适配设计:
- 版本解析:项目内置 Semver 解析器(支持
major.minor.patch[-preRelease][+buildMetadata]格式),并允许通过 Gradle 属性compose.version覆盖gradle/libs.versions.toml中声明的版本; - 场景实现分代:按 Compose Multiplatform 内部场景 API 的演进设置了两个分界点(barrier):
compose-scene-impl-1:基线版本,使用ComposeScene.render(canvas, nanoTime)旧 API;compose-scene-impl-2:从 1.12.0-alpha02(dev4213 起)开始,改用FrameRecomposer.performFrame()+ComposeScene.measureAndLayout()+ComposeScene.draw(canvas)拆分 API;compose-scene-impl-3:从 1.12.10-alpha01(dev4534 起)开始,额外要求运行期调用registerSkikoComposeImplementation()注册 Skiko 图形与文本实现;
- 自包含复制:每个实现模块都是前一个模块的完整自包含副本,刻意保留重复代码,以确保适配新 API 时绝不破坏旧版本仍能构建。
这一机制保证了"用旧版本跑基准"与"用新版本跑基准"都能正确编译并链接到与之匹配的场景实现,是version=参数与compare_benchmarks/find_degradation脚本能够跨版本对比的前提。
快速上手建议
- 首次体验:直接运行
./gradlew :benchmarks:run,使用默认的SIMPLE+VSYNC_EMULATION模式快速验证桌面端渲染性能; - 探索基准集:先执行
./gradlew :benchmarks:run -PrunArguments=listBenchmarks=true查看全部可用基准测试; - 针对性测量:使用
benchmarks=LazyGrid modes=REAL frameCount=200组合参数聚焦单个场景,REAL 模式需在带真实显示器的设备上运行; - 跨版本对比:需要评估升级影响时,用
./compare_benchmarks.main.kts v1=<旧版本> v2=<新版本> platform=macos一键对比; - 定位回退:发现劣化后,准备按从旧到新排序的版本清单文件,用
./find_degradation.main.kts benchmarks=<名称> versions=<版本文件>二分定位引入回退的版本; - CI 集成:无头环境建议使用
VSYNC_EMULATION模式并配合saveStatsToJSON=true,将 JSON 结果归档到统一目录做持续回归监测。
在阅读源码时,建议以 benchmarks/src/commonMain/kotlin/benchmarks 下的各基准测试实现为入口,结合 run_benchmarks.main.kts 了解脚本编排逻辑,再对照 settings.gradle.kts 理解版本适配机制,即可完整掌握这套跨平台性能基准体系的运行原理与扩展方式。
【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考