用 Java 而非 Rust 重写一切:词处理器性能与跨平台对比
[LOADING...]
Rust 非常适合内核、驱动或数据库引擎。这类代码不能因垃圾回收而暂停,必须精打细算每一个字节,Rust 的所有权模型在其中物有所值。但不知从何时起,“用 Rust 重写”成了万物的默认答案,桌面和移动应用也不例外。对于应用,我认为这是一笔错误的交易。你为编写、审查和构建代码付出更多,却没有换来更好的应用。
Codename One 是什么? Codename One 是一个开源框架,可用单一 Java 或 Kotlin 代码库构建原生 iOS、Android、桌面和 Web 应用。了解更多请访问 codenameone.com。
所以我们不争论,直接测试。Wordcraft 是一个用 Rust 和 egui 编写的开源文字处理器。它是一个严肃的项目,在 GitHub 上有超过 15,000 颗星,具备功能区、样式、表格、修订追踪和邮件合并,并发布了包含 502 项的功能清单,这使它成为一个公平的对比对象。我们用 Java 和 Codename One 构建了同一个文字处理器,实现了 Wordcraft 所实现的每一项功能,并在同一次 CI 运行中、相同的 GitHub 运行器上测量了两个应用。
Java 版本更快、更小、占用内存更少,代码量约为三分之二,运行测试的成本也低数倍。它用起来也更原生:有真正的 macOS 菜单栏,可将带格式文本复制到其他应用,还能在文档与操作系统其余部分之间拖放文本和文件。而且它用同一份源码发布到 iPhone 和 Android。详情见下文。
这也是忙碌一周的开始。文字处理器用一个应用完成了论证;本周余下时间则关乎你今天就能拿什么开始。三个新的全栈模板加入 Wayline:一款实时 3D 策略游戏、一个端到端加密通讯工具和一项外卖服务。每一个都是单个 Java 项目中的应用及其服务器,可自定义品牌并发布。
本周即将发布
今天是 Java 对阵 Rust 的故事。然后每天一个模板,随后是这些模板推动 Codename One 增加的框架功能:
- 10 月 24 日,星期六: Skirmish:一个 Java 多人策略游戏模板。一款《皇室战争》风格的实时策略游戏,拥有 3D 竞技场、在线对战、关卡编辑器、应用内购买、广告和审核控制台。
- 10 月 25 日,星期日: Parley:一个可定制品牌的私密通讯工具模板。一个端到端加密通讯工具,包含群组、频道、语音消息、语音和视频通话、关联设备以及管理控制台。
- 10 月 26 日,星期一: Savor:面向餐厅和本地配送车队的食品配送模板。客户、餐厅、骑手和管理员角色集于一个应用,支持银行卡预授权、实时追踪和付款结算。
- 10 月 27 日,星期二: 更多 Java API,以及 Unity 游戏实验。一个更大的 Java 类库、一项将简单 Unity 2D 游戏作为 Codename One 应用运行的实验、一个更快的 XML 解析器,以及文档中的可运行示例。
- 10 月 28 日,星期三: 原生菜单、文件对话框和 3D 模型。原生打开和保存对话框、带标准命令的原生菜单栏,以及动画 3D 模型。
- 10 月 29 日,星期四: WebRTC 通话、密钥协商和构建时 ORM 检查。WebRTC 通话、处处可用的密钥协商和 HKDF、带双向 TLS 的后端客户端,以及会让构建失败的 ORM 错误。
[LOADING...] Skirmish,星期六。 [LOADING...] Parley,星期日。 [LOADING...] Savor,星期一。## 数据
下面的每个数字都来自同一次 CI 运行(39825464193),该运行从源码构建两个应用,并在相同的机器上、针对相同的文档运行它们。工作流位于仓库中,因此你可以重新运行它。桌面端数值是在一份 1,000 段文档下,macOS / Linux / Windows 的中位数。
在 42 项桌面测量中,Java 赢了 38 项。图表以 macOS 上的比值展示差距,因此 2× 表示 Rust 版本需要消耗该资源的两倍:
[LOADING...]
其余四项差距很小,且没有一项能在所有操作系统上胜出:
启动近乎空白的文档时,4 毫秒的差异是人的感知不到的;而在另外两个系统上,同一项测量中 Java 领先数百毫秒。
它更快
输入延迟是用户能感受到的数字。用户在 Java 应用中输入一个字符,在 macOS 上 8.9 毫秒即可到达屏幕;Rust 应用则需要 51.9 毫秒。在 60 Hz 下,一帧持续 16.7 毫秒,因此一个应用能在一帧之内完成,另一个则要超过三帧。撤销也是同样的情况,8.9 毫秒对 52.3 毫秒。
打开文档也体现了这一点。重新打开测试 DOCX 在 macOS 上耗时 31.6 毫秒,Wordcraft 为 132 毫秒;在 Windows 上为 31.5 毫秒对 261 毫秒。
Java 在这里如何击败 Rust?Codename One 通过 ParparVM 对应用进行提前编译,先剔除应用永远不会触及的每个类和方法,再把字节码翻译成 C。随后由 C 编译器在剩余代码上进行内联和优化。运行时没有 JVM。垃圾回收让分配变得廉价,并在自己的线程上回收内存,因此应用代码无需做所有权记账、无需防御性克隆,也无需引用计数。UI 在一个线程上运行,而保存等工作在后台运行;保存时还会同时在多个核心上压缩大部分内容,不必围绕生命周期或 Send 约束来设计。我们没有为这个基准测试使用配置文件引导优化或特殊调优。这些都是每个 Codename One 应用默认获得的效果。
它更小,而且扩展性更好
“垃圾回收浪费内存”是我说 Java 应该用于桌面和移动应用时,人们告诉我的第一句话。测量结果却相反。可执行文件在每个操作系统上都不足三分之二大小,在 Linux 上只有一半。三个平台上的空闲内存都更低,而且差距随文档规模扩大:
对于 10,000 段的文档,Rust 版本需要多 65% 到 95% 的内存。在 macOS 上,打开该文档的启动时间为 917 毫秒对 1,670 毫秒。手动内存管理本身并不会让应用更精简。真正决定这一点的是数据结构,而垃圾回收器让你可以挑选好的数据结构,而不是只能挑选借用检查器接受的数据结构。
它对 AI 和 CI 的成本更低
Java 代码库的代码行数约为三分之二,token 数约为五分之三。如果你使用编码代理,token 就是成本模型:每个任务都从代理阅读代码开始,而它提出的每一处编辑都是由人审查的代码。相同的功能目录下,更小的代码库意味着更少的阅读量、更小的上下文和更小的差异。
这个文字处理器本身就是以这种方式写成的。我先用 Codex,后来改用 Claude;Codename One 内置的 MCP 服务器让代理可以在工作时驱动正在运行的应用并检查它。
CI 也说明了同样的事。在同一台运行器上,冷构建加完整测试套件,Java 应用耗时 97 秒,Wordcraft 耗时 961 秒。Java 测试在 JVM 上针对与原生发布相同的代码运行,因此测试运行不需要原生工具链。Rust 在运行任何东西之前,要先编译整棵依赖树和测试二进制文件。两个测试套件规模不同(1,123 个测试对 1,922 个),因此按每个测试计是 86 毫秒对 500 毫秒。Wordcraft 约有四分之一的测试覆盖滚动视口、弹窗放置和窗口状态这类底层管线,而 Codename One 把这些作为框架服务提供并只测试一次。
它更容易审查
下面是拼写检查器如何在不拖慢启动的情况下加载其 2 MB 词表,代码来自 Proofing.java:
任何 Java 开发者都能在几秒内审查这段代码。一个后台线程填充共享结构,内存由垃圾回收器负责。没有生命周期,没有 Arc<Mutex<...>>,也没有 Send 约束。
熟悉程度也很重要。每天阅读 Java 的开发者远多于阅读 Rust 的开发者,因此审查变更的人更可能熟悉这些惯用法。Java 对他们的要求也更少。审查者检查代码做了什么,而不是检查所有权、借用、生命周期和 trait 约束是否以某种恰好能编译的方式得到满足。这种更轻的心智负担是真实的成本差异。当一个代理为你写了 500 行代码时,人类多快能审查完它们,决定了你推进得多快。
它更具可移植性
同一套源码可构建原生 iPhone 和 iPad 应用、Android 应用、浏览器应用,以及 macOS、Linux 和 Windows 桌面应用。CI 在每次运行中都会构建所有这些目标,并附上每个目标的截图。在手机上,功能区会折叠成标签选择器,页面会按屏幕宽度重排,触摸工具栏会取代状态栏。Wordcraft 没有移动端目标。
有一个 Wordcraft 支持而我们不支持的平台:FreeBSD。Codename One 目前不以它为目标。如果对 FreeBSD 支持有需求,我们会加上,所以请在评论中告诉我们。
[LOADING...] iPhone
[LOADING...] Android
[LOADING...] 同一个应用在浏览器中。
它感觉更原生
这是我最在意的部分,因为用户在看到任何基准测试之前就能感受到它。可移植性通常意味着最低共同标准。这里恰恰相反:Java 应用比 Rust 应用更深入地与操作系统集成。在 Mac 上,它有真正的菜单栏和标准快捷键,能复制 Pages 和 Mail 可正确粘贴的格式化文本,还能把选区拖到另一个应用中,或从访达拖入文件和文本:
[LOADING...] macOS 上的原生菜单栏,带有子菜单。
[LOADING...] “关于”、“设置”和“退出”出现在 Mac 用户期望的位置。周三的文章展示了任何应用如何获得这些能力。
它看起来至少一样好
Wordcraft 在左边,Java 应用在右边。两者都是精致的桌面应用,并且都实时跟随操作系统的深色模式。但只有一个在手机上看起来也正确,并遵循高对比度设置。
[LOADING...] Wordcraft(Rust)。截图来自其 README。
[LOADING...] macOS 上的 Java 版本。
[LOADING...] 深色模式,实时跟随操作系统。
它同样安全
内存安全是大多数人选择 Rust 的理由,而且这是个好理由。Java 自 1995 年以来就是内存安全的。边界会检查,没有悬垂指针,也没有手动 free。值得称赞的是,Wordcraft 在自己的 crate 中禁止 unsafe,因此就内存安全而言,这两个应用不相上下。
区别在于供应链。Wordcraft 的 Cargo.lock 引入了 534 个外部 crate。Java 应用依赖 Codename One 框架及其自身模块。每个依赖都是有人必须信任、更新和审计的代码,因此更小的依赖树意味着更小的攻击面。
Rust 能追上吗?
在体积和速度方面,也许能。但这需要大量工作,并要深入重新思考内存布局和内联,而这又意味着更多代码和更慢的构建。要在这些方面赶上 Java,就意味着在 Rust 内部重建 Java 已经提供的东西。这就是我所说的成本。
说点个人感受。我每天都用 Firefox,也喜欢 Rust 相关工作为它带来的速度和稳定性提升。但 Firefox 是个特例,而且我认为它恰好证明了我的观点。Rust 替换的是底层部分:样式引擎、渲染器、媒体解析。这正是 Rust 擅长的领域,而且它在那里表现得很好。浏览器的用户界面,也就是高层部分,是用 JavaScript 写的——一种 JIT 编译、带垃圾回收的语言,给了团队灵活性。Firefox 实际上为自己重新发明了 Java 的高层部分。浏览器必须同时拥有这两部分。而你的应用只有第二部分。
为什么这现在很重要
我在 2018 年写 Create an Uber Clone in 7 Days 时,目标是构建一个 Uber 风格的应用。今天,这应该是你的起点。借助编码代理,一个开发者可以承担过去需要团队才能完成的范围。Hello world 不再只是屏幕上的一个标签。它是一个完整应用,带有后端、管理控制台、支付和推送通知,而你的工作从那里开始。
在这种规模下,语言的开销会不断累积。每一行额外代码都是代理要写、人类要审查、CI 要在每次变更时构建的内容,而且会贯穿产品的整个生命周期。Java 就是为许多人维护的大型代码库而设计的,这一点很明显。它拥有庞大的生态系统、可读的代码、快速的编译器、快速的测试,以及一个把内存管理从你手上接过去的垃圾回收器。把这份预算花在设置界面里的生命周期上,不会为用户带来任何价值。
这就是 Codename One 正在走的方向:完整的起点,加上真实产品在代码之外所需要的一切。
每个模板都是一个可运行的应用程序及其服务器,位于同一个 Java 项目中。客户端和服务器共享 API 类型,因此 REST 调用或 WebSocket 消息在两端都会进行类型检查。增长和变现能力内建在框架中,而不是事后加装:邀请好友链接、分析、推送通知、广告和应用内购买都是 Codename One API,模板已经在使用它们。商务是我们正在做的下一部分。模板源码在 MIT-0 下免费提供,无版税,也无收入分成。## 本周更多内容
一款免版税的实时 3D 策略游戏
多年来,我一直避免把 Codename One 推荐用于游戏。但在我们构建这些模板时,这种情况改变了:一款现代移动游戏主要是一个应用。登录、菜单、商店、好友、任务、推送和服务器,而战斗只占代码的一小部分。Skirmish 是一款《皇室战争》风格的实时策略游戏。两名玩家在由带动画的 glTF 角色组成的 3D 竞技场中面对面,各自从牌组中召唤部队和法术,并试图摧毁对方的塔。对局通过 WebSocket 以锁步方式在线运行,并带有回放、进度系统、宝石包、赛季通行证、广告和邀请。
视频中的每个菜单都是普通的 Codename One 组件,使用布局管理器进行布局,并用 CSS 设置样式。
[LOADING...] 主界面、宝箱和任务。 [LOADING...] 在线对局。 [LOADING...] 商店。
游戏本身只是交付内容的一半。The Forge 是游戏内置的关卡编辑器,玩家和你的设计师可以在其中构建单位、竞技场、任务和完整战役,并将它们作为资源包分享。还有一个管理控制台,像运营在线服务一样运营游戏:实时对局、举报与标记、带可审计审核的玩家历史、游戏数据查询、用户参与流水线和促销活动。它既能在手机上运行,也能在桌面端运行。
[LOADING...] The Forge,关卡编辑器。 [LOADING...] 设计竞技场。 [LOADING...] 管理控制台。 [LOADING...] 桌面端实时运营。 [LOADING...] 用户参与流水线。
向游戏中添加一个单位,只需在共享规则模块中写一行代码:
没有版税,没有按安装量收费,也没有收入分成。周六的 Skirmish 文章 详细介绍了竞技场、多人模式以及商业化方式。
一款服务器无法读取的即时通讯应用
医院、企业和社区都想要自己的即时通讯工具,但无法为此使用 WhatsApp。Parley 就是这样一款通讯应用:聊天、群组、频道、语音消息、语音和视频通话以及关联设备,均使用 X3DH 和 Double Ratchet 进行端到端加密,这正是 Signal 采用的设计。服务器只存储和转发密文,通话则采用点对点方式。
[LOADING...] 聊天、群组和频道。 [LOADING...] 加密对话。 [LOADING...] 点对点通话。 [LOADING...] 无需阅读消息即可进行审核。
加密代码读起来就像普通 Java,因为这些密码学原语现在已成为 Codename One 的一部分:
周日的 Parley 文章 介绍了协议、内容审核以及如何为它打造品牌。
没有平台抽成的外卖服务
外卖平台通常会从每笔订单中抽取 15% 到 30%。Savor 为餐厅、连锁品牌或本地创业公司提供属于自己的服务:一个应用,其首页会根据角色(顾客、餐厅、配送员或管理员)而变化;银行卡预授权在配送时扣款;在地图上实时跟踪,且无需支付 API 密钥费用;订单和配送状态机不会在两个人同时操作时发生混乱。
[LOADING...] 顾客 [LOADING...] 餐厅 [LOADING...] 配送员 [LOADING...] 管理员
周一的 Savor 文章 展示了这一流程,以及从单一餐厅到杂货或鲜花配送的各种重塑方式。
更多 Java 内容,以及一个关于 Unity 的问题
类库新增了 116 个类和接口,包括 java.util.function、流、Optional、java.time 以及读取器和写入器。现有 Java 代码在移植时需要的改动更少,ParparVM 和混淆器会剔除应用未调用的内容,因此开销很小。我们仍然有意不支持反射和类加载。
我们还尝试了一项新事物,从 Unity 做得好的地方学习:简单的 Unity 2D 项目,包括 C# 脚本在内,现在可以构建并作为 Codename One 应用运行。五款开源游戏未经修改即可运行。这是一个概念验证,你并不能拿任意 Unity 游戏来运行。
[LOADING...] Fruitopia [LOADING...] 推箱子
这对你有用吗?周二的 Java 与 Unity 文章 提出了这个问题,还介绍了一个更快的 XML 解析器,以及 API 文档中一个可在浏览器中运行每个示例的按钮。
拥有原生体验的桌面应用
这款文字处理软件需要真正的菜单栏和真正的文件对话框,才能与原生应用相比不落下风,所以现在每个 Codename One 应用都拥有这些能力。只需一次调用,Command 就会进入原生菜单栏,标准命令会出现在各操作系统期望的位置,并使用各自的快捷键:
在 Mac 上,它是带 Cmd+, 快捷键的“Settings...”;在 Windows 上,它是“Tools”下的“Options”;在 Linux 上,它是“Edit”下的“Preferences”。打开和保存对话框都是操作系统自带的,带有筛选器、选项,并在平台支持时提供预览:
[LOADING...] 带有预览和选项的打开对话框。 [LOADING...] macOS 应用程序菜单中的标准命令。
周三的 桌面端文章 介绍了细节,以及 Skirmish 背后的动画 3D 模型。
通话、加密,以及会让构建失败的 ORM
语音和视频通话现在是一个 Codename One API,在 Android、iOS 和浏览器上基于 WebRTC 构建。信令仍由你掌控,因此同一份代码可与任何服务器配合使用:
将密钥协商结果转换为加密密钥只需两行代码,并且在每台设备和服务器上行为一致:
在服务器端,一个要求客户端证书和 HTTP/2 的合作伙伴 API,只需一个构建器:
而在使用 Hibernate 和 Spring 时,大量映射错误都能顺利编译,却会在应用启动时,或在第一个触及该实体的请求出现时爆发。Codename One 的 ORM 会在编译时读取你的实体,因此下面这些问题都会成为构建错误:
周四的 通话与后端文章 逐一介绍了这些内容。
这一切将走向何方?
我不希望这被解读为“Rust 不好”。如果你在编写内核模块、编解码器、嵌入式控制器,或任何有硬实时限制的东西,确定性比吞吐量更重要,那么 Rust 是极佳的选择。面向用户的应用则有不同的约束。它可以牺牲确定性,来换取吞吐量、更小的代码库,以及更快的团队节奏。
对于应用而言,Java 是更好的工具,而 Codename One 正在成为一个让你从完整产品而非空白屏幕起步的地方。这款文字处理软件现已出现在 演示页面 上。克隆 代码仓库,重新运行基准测试工作流,并核验我的数字。如果你发现我某项测量有误,请在那里提交 issue。如果你想今天就启动一个项目,Initializr 可以在一分钟内生成任意模板。