请先读这里
没有这些,下面的表格是读不懂的。它们来自基准测试自己的文档,不是我们的说法。
这里的一切都测自一部 Redmi Note 10 Pro(sweet,Adreno 618,1080×2400,120 Hz,Android 12),release 构建,每阶段 3 次迭代,每次迭代启动一次应用。这是五个框架在同一台设备上的比较,不是对其他任何设备的断言。
屏幕以 120 Hz 运行,多个场景在不止一个框架上触及该上限。触顶的行只说明"该框架在这里不是瓶颈",仅此而已;要区分它们需要更重的负载,这正是基准测试的 CFBENCH_LOAD 的用途。
Flashlight 统计的是应用窗口的呈现,在五个平台中的四个上这与场景开销无关——它为一个什么都不画的 React Native 阶段读出了恰好 60.00,并且在 120 Hz 屏幕上的 3388 次采样中从未超过 60.00。所以帧率是每个应用对自身的测量;Flashlight 提供的是 CPU 和内存,这是任何应用都无法自测的。
逐阶段帧率
每个应用对自身的测量——五个平台中有四个,外部性能分析器测的是窗口而不是场景。越高越好。
| 阶段 | CrossFly | Flutter | React Native | Kotlin Multiplatform | .NET MAUI |
|---|---|---|---|---|---|
draw_rect 16 000 filled rects in a full-screen grid | 104.9 | 9.3 | 10.3 | 57.7 | 34.7 |
draw_text 16 000 short text runs | 55.2 | 6.7 | 13.9 | 9.9 | 8.0 |
draw_image 16 000 image quads from one texture | 87.9 | 7.6 | 12.7 | 35.2 | 27.8 |
cards as many realistic cards as fit at a fixed font scale | 118.8触顶 | 49.0 | 75.4 | 103.1 | 74.0 |
textfield 2400 real, interactive TextFields | 56.4 | 12.8 | 0.06 | 17.9 | 18.7 |
image_one_texture a 48×48 quad grid from one atlas page | 118.5触顶 | 53.9 | 72.6 | 119.7触顶 | 120.0触顶 |
shapes_lines a batched line every 1px across the surface | 111.4 | 29.3 | 120.1触顶 | 119.8触顶 | 119.9触顶 |
shapes_circles 50 concentric filled circles | 99.1 | 40.9 | 107.3 | 112.9 | 105.1 |
shapes_polygon_concave 3 nested 36-point concave stars | 118.6触顶 | 119.2触顶 | 89.0 | 117.2 | 87.4 |
shapes_polygon_convex 3 nested 36-side convex polygons | 119.6触顶 | 119.3触顶 | 120.0触顶 | 120.0触顶 | 120.0触顶 |
shapes_shadow 8 nested rounded rects with a 40px blurred shadow | 119.9触顶 | 110.2 | 119.4触顶 | 120.0触顶 | 120.0触顶 |
shapes_text 16 offset passes over a screenful of text | 80.3 | 46.3 | 117.6 | 73.6 | 44.1 |
sqlite_validate that the encryption below is real | 11/11 pass | 7/7 pass | 3/5 pass | 未实现 | 7/7 pass |
sqlite_bench 100k rows × {plain, ChaCha20, SQLCipher} | 3 ciphers | 2 ciphers | 2 ciphers | 未实现 | 2 ciphers |
带底色的单元格已达到屏幕 120 Hz 上限。它们只说明“该框架在这里不是瓶颈”,仅此而已——是下限,不是结果。
shapes_circles —— 50 个同心实心圆,约 3500 万像素的不透明重绘 —— 在四个移植版中的三个上跑得更快。两边做的是同样的工作:它们都是这块 GPU 的 OpenGL ES 驱动上的 Skia,其帧时间与绘制面积成线性关系,和参考实现完全一样,区别在于每像素的代价。在这里参考实现受限于自己的三角剖分,是 CPU 瓶颈。
内存才是各框架真正拉开差距的地方
十四个阶段的 PSS 峰值。下限是一个框架在画任何东西之前就要收的费用;上限是它最糟糕的场景的代价。
下限就是运行时加上 GL 上下文——在第一个像素之前就要付的门票钱。
2400 个原生文本框 —— EditText、TextInput、Entry、BasicTextField —— 要花 321 到 601 MB。CrossFly 自己绘制这 2400 个,只花 132 MB,比它自身的下限高 14 MB。代价不在于绘制文本框,而在于把它们作为平台对象持有。
最终交付物
Release、已签名、仅 arm64-v8a —— 否则比较的是打包惯例,而不是框架。
压缩后的构建并不是被测量的那些,MAUI 除外——它只能这样构建。而且压缩表按现状无法排名:Kotlin Multiplatform 没有实现 SQLite 阶段,因此不携带任何引擎,而参考实现的引擎占其 2.66 MB 中的 1.82 MB。减掉它,两者相差 0.04 MB。
SQLite:差距在绑定层
100 000 行,所有平台使用相同 SQL、相同 journal mode 与 synchronous 设置。React Native 的耗时被排除,因为它自己的验证阶段有两项未通过——那正是该阶段存在的意义。
| 测量项 | CrossFly | Flutter | React Native | .NET MAUI |
|---|---|---|---|---|
| plain insert, 100 000 rows | 216 ms | 251 ms | — | 1248 ms |
| rows/s | 463 000 | 398 000 | — | 80 100 |
| plain bulk insert | 154 ms | 232 ms | — | 1242 ms |
| ciphers run | plain, chacha20, sqlcipher | plain, sqlcipher | plain, sqlcipher | plain, sqlcipher |
| validation | 11/11 | 7/7 | 3/5 | 7/7 |
你与 GPU 之间的层级更少
每一帧多走一跳都要花时间。CrossFly 通过 LibGDX 直接绘制——没有 JavaScript 桥要跨越。
上述数字的全部限定条件
完整保留,不做概括。没有限定条件的基准测试就是广告。
这里的一切都测自一部 Redmi Note 10 Pro(sweet,Adreno 618,1080×2400,120 Hz,Android 12),release 构建,每阶段 3 次迭代,每次迭代启动一次应用。这是五个框架在同一台设备上的比较,不是对其他任何设备的断言。
屏幕以 120 Hz 运行,多个场景在不止一个框架上触及该上限。触顶的行只说明"该框架在这里不是瓶颈",仅此而已;要区分它们需要更重的负载,这正是基准测试的 CFBENCH_LOAD 的用途。
Flashlight 统计的是应用窗口的呈现,在五个平台中的四个上这与场景开销无关——它为一个什么都不画的 React Native 阶段读出了恰好 60.00,并且在 120 Hz 屏幕上的 3388 次采样中从未超过 60.00。所以帧率是每个应用对自身的测量;Flashlight 提供的是 CPU 和内存,这是任何应用都无法自测的。
以 120 fps 运行的场景提交的帧数是 14 fps 场景的八倍,因此它的 CPU 更高,原因与效率毫无关系。比较两个单元格之前,先除以帧数。
在未改动的构建上,draw_*、textfield 和 shapes_text 各行在不同轮次间就会有这么多漂移,因此小于该幅度的差异是噪声。
Kotlin Multiplatform 没有 SQLite 阶段,它报告这一缺口而不是跳过。React Native 的 SQLite 验证有两项未通过——那正是该阶段在说旁边的耗时不可信——所以这些耗时被排除而非发布。
基准应用、四个移植版和测量脚本都在同一个仓库里。每个平台大约十二分钟。
flashlight/run-all.sh flashlight/results/crossfly dev.crossfly.benchmark python flashlight/summarize.py flashlight/results/crossfly