Executive summary
结论先行:一份 Core,三种高频边界
“只维护一套源码”约束的是业务事实源,不意味着三端必须共享同一种 ABI、对象表示和界面调度机制。
直接使用 KMP/JVM
共享代码与原生 Kotlin 运行在同一 JVM/ART 体系内,不需要为普通或高频接口增加另一套桥接工具。
Framework + C ABI 双通道
普通接口继续使用官方 Kotlin/Native Framework;高频批量通道从同一 Core 导出窄 C ABI。
CPF-KMP + Direct Buffer
使用 Kotlin/Native 编译共享 Core,以 AKInterop/N-API 同步借用 ArrayBuffer,避免重型对象桥接。
KMP 本身不等于内存泄漏。无界输入队列、重复完整快照、跨边界对象图、缓冲区多次拷贝和生命周期未释放,才是高频数据链路中需要被硬性限制的风险。
Methodology
先保证结果一致,再比较性能
三端使用同一场景结构和最终状态校验。快但算错的实现,不进入性能结论。
为什么同时看中位数和尾部
中位数表示一半样本快于该值,适合观察典型表现;第 95 百分位表示 95% 的样本不超过该值;第 99 百分位专门暴露偶发抖动。高频链路里,尾部抖动比平均值更容易形成队列积压。
短场景在同一设备上交错运行多个方案,每条车道执行 5 轮。内存同时记录测试前、测试后和最高点,不用单个截图替代完整过程。
为什么还要跑真实时间长稳
把几万次调用压缩到数秒,只能证明吞吐上限。垃圾回收节奏、队列水位、前后台切换和停止后的释放,需要按每秒 20 批、每秒 100 批的真实速率持续运行才能观察。
不同平台的内存统计口径和设备基线不同,因此本文只在同平台、同设备、同负载内比较方案,不用 iOS 数字与 HarmonyOS 数字做平台排名。
Shared-core boundary
共享业务语义,适配平台边界
Core 保持单一事实源;网络、线程、页面生命周期、崩溃采集和 UI 渲染仍属于平台层。
Android
KMP/JVM 直接 API → 原生 Kotlin Adapter → UI
iOS
官方 Framework(普通接口)/ C ABI 批量缓冲区(高频接口)→ Swift Adapter → UI
HarmonyOS
CPF-KMP Kotlin/Native → AKInterop/N-API Direct ArrayBuffer → ArkTS Adapter → UI
跨端契约可以一致,内存表示可以不同。平台 Adapter 只做序列化、调度、生命周期和界面 Patch 消费,不复制一份业务规则。
iOS benchmark
Framework 已可用,C ABI 留给高频批量通道
iPhone 17 Pro Max · iOS 26.5.1(Build 23F81) · Release · 每方案 5 轮
Measured on device
iOS 常态与突发尾部延迟
单批 Core 处理耗时 · 越低越好
| 方案与指标 | 实测值 |
|---|---|
| Swift 原生 · 常态第 95 百分位 | 0.026 毫秒 |
| Swift 原生 · 突发第 99 百分位 | 0.028 毫秒 |
| 官方 KMP Framework · 常态第 95 百分位 | 0.107 毫秒 |
| 官方 KMP Framework · 突发第 99 百分位 | 0.115 毫秒 |
| KMP C ABI · 常态第 95 百分位 | 0.094 毫秒 |
| KMP C ABI · 突发第 99 百分位 | 0.103 毫秒 |
Measured on device
iOS Core 理论处理余量
测试进程内每秒完成批次 · 越高越好
| 方案与指标 | 实测值 |
|---|---|
| Swift 原生 · 常态 | 44,708 批/秒 |
| Swift 原生 · 突发 | 45,731 批/秒 |
| 官方 KMP Framework · 常态 | 10,228 批/秒 |
| 官方 KMP Framework · 突发 | 10,268 批/秒 |
| KMP C ABI · 常态 | 11,696 批/秒 |
| KMP C ABI · 突发 | 11,699 批/秒 |
Measured on device
iOS 最高常驻内存
同一完整场景的进程最高值 · 越低越好
| 方案与指标 | 实测值 |
|---|---|
| Swift 原生 | 130.47 MiB |
| 官方 KMP Framework | 148.53 MiB |
| KMP C ABI | 136.70 MiB |
保留官方 Framework 作为默认开发接口,避免为低频调用增加 C API 维护成本;只把高频、批量、可扁平化的输入与 Patch 输出收敛到 C ABI,并在 Swift Adapter 中完成缓冲区管理、latest-only UI Patch 和页面退出释放。
Android benchmark
直接使用 KMP/JVM,不增加第二层桥接
Redmi K20 Pro · Android 11 · Release · 每方案 5 轮
Measured on device
Android 常态与突发尾部延迟
原生 Kotlin 与 KMP/JVM · 越低越好
| 方案与指标 | 实测值 |
|---|---|
| 原生 Kotlin · 常态第 95 百分位 | 0.206 毫秒 |
| 原生 Kotlin · 突发第 99 百分位 | 0.239 毫秒 |
| KMP/JVM · 常态第 95 百分位 | 0.118 毫秒 |
| KMP/JVM · 突发第 99 百分位 | 0.176 毫秒 |
Measured on device
Android 最高 PSS
进程实际占用近似值 · 越低越好
| 方案与指标 | 实测值 |
|---|---|
| 原生 Kotlin | 119.22 MiB |
| KMP/JVM | 137.43 MiB |
KMP/JVM 与原生 Kotlin 共享 JVM/ART 对象体系。用普通 Kotlin API 直接调用 Core,并在契约层使用批量结构即可;额外引入 C ABI 或 JavaScript 桥接只会增加复制、调试和版本治理成本。
HarmonyOS benchmark
运行时和桥接路径决定内存底座
Mate 60 Pro · HarmonyOS 6.x / OpenHarmony 6.1.0.115 · Release
Measured on device
HarmonyOS 完整流程最高 PSS
同机不同实现的全流程最高值 · 越低越好
| 方案与指标 | 实测值 |
|---|---|
| ArkTS 原生 | 247.08 MiB |
| KMP/KNOI 优化前 | 539.60 MiB |
| KMP/KNOI 优化后 | 405.40 MiB |
| CPF-KMP + AKInterop | 83.39 MiB |
Measured on device
HarmonyOS 高频边界优化阶梯
从生命周期治理到更换桥接路径 · 越低越好
| 方案与指标 | 实测值 |
|---|---|
| KMP/KNOI 优化前 | 539.60 MiB |
| KMP/KNOI 优化后 | 405.40 MiB |
| CPF-KMP + AKInterop | 83.39 MiB |
Measured on device
CPF-KMP 长稳关键内存节点
31.3 分钟、1802 个逐秒样本 · 越低越好
| 方案与指标 | 实测值 |
|---|---|
| 开始采样 | 72.13 MiB |
| 常态输入最高点 | 71.41 MiB |
| 高压输入最高点 | 83.39 MiB |
| 停止输入后恢复 | 45.58 MiB |
新方案仍使用 Kotlin/Native,不是 Kotlin/JS。ArkTS 通过 N-API 同步传入可借用的 ArrayBuffer,Kotlin 侧在调用期间读取扁平数据,返回后不保存外部指针。普通低频接口仍可保留声明式调用,高频通道必须保持窄、批量、同步和可测。
Memory governance
七条规则把空间复杂度写进架构
性能优化不能只靠一次合并间隔或一次垃圾回收;队列、对象数量、缓冲区所有权和退出释放都必须有硬边界。
01 · 队列必须有界
消费落后时按键合并、最新值覆盖或执行明确丢弃策略,不能让输入批次无限排队。
02 · 边界传扁平批量数据
跨语言调用传二进制批次、索引和长度,不逐字段创建两套对象图。
03 · 缓冲区复用且不越界持有
调用方预分配,Core 只在同步调用期间借用;返回后不保存外部指针。
04 · 只输出增量 Patch
状态只保留一份,界面只接收变化范围,不在每个批次复制完整表。
05 · 界面侧只保留最新待渲染值
渲染速度低于输入速度时合并刷新,避免 UI 反向撑大 Core 队列。
06 · 生命周期必须显式闭环
取消订阅、切换数据源、进入后台和退出页面都执行明确的停止与释放。
07 · 短测与长测回答不同问题
短测看尾部延迟和吞吐;长测看内存斜率、最高点、恢复值和积压量。
首次完整场景记录最高内存;10、30、120 分钟真实速率长稳记录内存斜率和队列深度;停止输入后观察恢复;再覆盖取消、重进、前后台和断线重连。任何阶段都不允许靠无界排队维持“零丢包”。
Decision matrix
最终工程选型
共享的是业务 Core 和契约语义;平台边界根据运行时与高频调用成本选择。
| 平台 | 默认通道 | 高频批量通道 | 结论 |
|---|---|---|---|
| Android | KMP/JVM 直接 API | 同一直接 API + 批量结构 | 直接使用共享 Core |
| iOS | 官方 Kotlin/Native Framework | 同一 Core 的 C ABI + Swift Adapter | Framework 与 C ABI 双通道 |
| HarmonyOS | CPF-KMP 普通接口 | AKInterop/N-API Direct ArrayBuffer | CPF-KMP 直接缓冲区通道 |
业务代码如何保持一套
模型、合并、排序筛选状态、计算规则、增量 Patch 语义和 Golden 校验全部放在 commonMain。三端 Adapter 不得重新实现规则,只负责把平台数据转换为同一批量契约。
出问题如何定位
每个边界记录输入批次、处理批次、丢弃/合并次数、队列深度、Core 对象上限和 UI 待消费 Patch 数;崩溃产物保留各端符号。这样可以先判断问题位于输入、Core、桥接还是 UI,而不是把所有增长都归因于 KMP。
Limits & reproducibility
结论有效,但不是永久免测
设备、系统、编译器、GC 和桥接工具升级都可能改变结果;正式接入仍需真实页面生命周期验证。
- 本数据证明三个推荐方案在当前负载内达到性能与内存门槛,不证明任意 KMP 代码都自动高效。
- HarmonyOS 的 31.3 分钟长稳已经回答验证工程内的有界性,正式页面仍应补齐 120 分钟、前后台、重连、取消和退出重进。
- iOS 的 C ABI 只服务真实高频批量通道;普通接口优先保持 Framework 的类型安全和维护效率。
- 所有内存值都应结合队列深度、Core 对象数和停止后的恢复值解释,不能只看某一秒的进程数值。
HarmonyOS 复现工具链
| 项目 | 版本 |
|---|---|
| Kotlin Gradle Plugin | 2.2.21-0.4.0-08 |
| CPF Kotlin 源码标签 | v2.2.21-0.4.0 |
| Gradle | 8.9 |
| AKInterop Gradle Plugin | 0.0.1 |
| AKInterop KSP | 2.2.21-2.0.4 |
相关开源项目与测试版本
这里列出本文涉及的开源项目及本次验证使用的版本,不是三端性能 Demo 的下载入口。项目名称、完整 URL 和操作入口均可打开对应公开仓库。
CPF-KMP Kotlin
HarmonyOS 使用的 Kotlin/Native 编译器与标准库源码。
https://gitcode.com/CPF-KMP-CMP/kotlin本次测试锁定提交打开源码仓库 →0df42d4f7eaff2f1a5a0f763de4ababc875992f2AKInterop
Kotlin 与 HarmonyOS N-API/ArkTS 之间的直接缓冲区互操作工具。
https://gitcode.com/CPF-KMP-CMP/akinterop本次测试锁定提交打开源码仓库 →b8b80eaa44b84bc84ee883a2c86224c07fbf2a11KuiklyBase
原有 HarmonyOS KMP 运行时与平台适配方案,对照测试来源。
https://github.com/Tencent-TDS/KuiklyBase-platform对照方案源码打开源码仓库 →公开仓库KNOI
KuiklyBase 面向 HarmonyOS 的 Kotlin 注解桥接代码生成工具。
https://github.com/Tencent-TDS/KuiklyBase-components/tree/master/knoi对照方案源码打开源码仓库 →公开仓库
采用 KMP 共享逻辑,不等于把所有平台细节塞进 commonMain。最稳妥的方案是:业务语义只维护一套,热路径按平台选择最低成本边界,内存治理写成可观测、可回归、可拒绝上线的硬规则。