React Native 并不在 V8 或 JavaScriptCore 上运行你的 JavaScript,而是跑在 Meta 为手机打造的引擎 Hermes 上。关于它“启动快、内存低”的说法流传已久,但很少有人拿一段像真实应用那样的代码,去和熟悉的引擎做同场对比。于是有人按 React Native 0.88 实际搭载的版本构建了 Hermes,把同一批脚本分别跑在它和 Node 上。
![]()
结论可以先用一句话概括:Hermes 启动时间只有零头,内存占用也只有零头,在 JSON 处理上跟得上 V8,但在大多数其他任务上比 V8 慢2 到 5 倍,其中正则替换是测到的最差情况。
测试是怎么搭的
Hermes 用的是 1.0.0 版本,从 hermes-v260318099.0.5 标签构建,正是 React Native 0.88.1 依赖的 hermes-compiler 版本,Release 构建,字节码版本 99。对照方是 Node 24.7.0(内含 V8 13.6),分别以正常模式和 --jitless 模式运行。机器是一台 Apple M4 MacBook Air,系统 macOS 26.3.1。
--jitless 这一组很关键。iOS 不允许应用在运行时生成机器码,所以 iOS 应用里的 JavaScript 引擎都拿不到 JIT。关掉 JIT 的 V8,比正常 Node 更接近手机上实际能做到的水平。Hermes 本身也不用 JIT:这个构建带一个 -Xjit 标志,但会回答“JIT is not enabled in this build”。
Hermes 的每个脚本都跑两种方式:从源码运行,以及用 hermesc -O -emit-binary 提前编译成字节码再运行——后者才是 React Native 发布构建实际打包的东西。每个测试作为独立进程跑 7 次,表格取中位数,波动最多几个百分点。
启动:字节码才是主场
三个脚本:一个只做打印;一个加载 lodash(544 KB,带完整注释的构建);一个 5.4 MB 的文件,由十份 lodash 各自放在独立函数作用域里拼成,用来模拟大型应用包。计时覆盖整个进程从启动到退出。
这正是 Hermes 被造出来的原因。从字节码启动,大包只用了 9 毫秒,进程内存峰值 13 MB;Node 则用了 69 毫秒和 98 MB。
从源码启动则是另一幅景象。Hermes 在启动时编译这个 5.4 MB 的包,花了 1.6 秒和 200 MB。V8 在这方面强得多,因为它惰性解析,只在函数被调用时才编译。所以 Hermes 只有配合提前编译好的字节码才有意义,而 React Native 的发布构建正是这么做的。开发模式下应用从源码运行,debug 模式启动慢,说明不了发布版本的表现。
跑应用代码:JSON 之外全面落后
四个工作负载,都按应用代码常见的写法来写:
- JSON:解析并序列化一个 5.6 MB 的信息流响应,含 20000 条帖子,嵌套用户、标签和位置
- 列表:对 100000 条帖子做过滤、映射和排序,类似为长列表准备数据
- 字符串:用 toLowerCase 和正则替换拼邮箱地址、校验地址、用正则拆出日期、用模板字符串拼展示标签
- 对象:创建 300000 个小类实例并映射成 props 对象
计时按每次迭代计,在脚本内部测量。字节码和源码在这里速度相同,所以展示的是字节码数字。
JSON 是 Hermes 唯一跟得上的地方。解析速度与 V8 持平,略快于正常 Node、略慢于 --jitless;序列化则比两者都快约 13%。这不是小事——解析 API 响应占了应用 JavaScript 的很大一块。
其余项目上,Hermes 比开着 JIT 的 V8 慢 2 到 5 倍:创建对象约慢一倍,列表慢四倍,字符串慢五倍。它也比关掉 JIT 的 V8 慢:对象约慢 13%,列表和字符串约慢一倍。所以“Hermes 很快”这句话对启动成立,对执行代码不成立。对大多数界面来说这没关系,排序 100000 条要 80 毫秒,这种事不会每帧都做。但对重活来说,这就是把工作量压小、或者挪到原生代码里去的理由。
内存:只有 Node 的 29% 到 58%
同样几组运行,整个进程的峰值常驻内存:Hermes 只用了正常 Node 的 29% 到 58%。在手机上,系统会杀掉占用过高的应用,这一点比几毫秒的差距更重要。
字符串的时间花在哪
字符串是差距最悬殊的一组,于是被拆开逐项计时:50000 个短名字,每个操作单独计时。toLowerCase 和模板字符串都没问题,慢的是正则替换——Hermes 比关掉 JIT 的 V8 慢三倍,比正常 V8 慢十倍。在 Hermes 上,纯字符串的 replaceAll 比正则版本快三倍,不过它只匹配单个空格,不能直接替换。如果应用的热路径要对列表里每一项跑一次正则,这就是第一个该看的地方。
字节码比压缩后的源码更大
字节码启动快,但它并不小。对着带注释的源码比,字节码看起来小三倍;对着压缩后的文件比——这才是公平的比较——它大2.5 倍,gzip 之后大将近4 倍。所以发布字节码是用下载体积换启动时间。对多数应用这笔交易划算,但值得知道账单落在哪一边。
几处需要说明的地方
第一次做体积对比时用的是完整的 lodash.js,字节码显得小三倍,但那个文件大部分是注释;换成压缩构建后结论正好相反。原本还打算测 Hermes 的 JIT,标志存在,但这个构建不含 JIT,React Native 也不开启它,所以这里所有 Hermes 数字都来自解释器。另外第一次在 Node 里加载 lodash 时脚本报告找不到它,原因是 lodash 看到 Node 的 module 对象后通过它导出,而不是挂到全局;把 module 和 exports 在包装层里藏起来之后才正常。
还有几点边界:这是笔记本不是手机,M4 比中端安卓机快得多,真机上的绝对数字会高出好几倍,可信的是引擎之间的比例,而且换个 CPU 比例也可能变。Node 不是会装进应用里的引擎,它在这里只是 V8 的参照。JavaScriptCore 没有测——那是 Hermes 之前 React Native 在 iOS 上用的引擎。5.4 MB 的包是同一个库的十份拷贝,字节码去重效果很好,它 1.0 MB 的字节码体积并不典型。计时包含进程启动,这也是空脚本出现在表里的原因。工作负载是自选的,真实应用的代码可能偏重没测到的东西。
可以带走的几条
- 发布构建一定要发 Hermes 字节码。从源码启动大包要 1.6 秒,从字节码只要 9 毫秒。
- 别在 debug 构建里判断启动性能,它跑的是源码。
- Hermes 在 JSON 上很快,用 JavaScript 解析大响应没问题。
- 普通 JavaScript 比在 Chrome 或 Node 里慢好几倍,重循环要在 Hermes 上测,不要在浏览器里测。
- 盯住热路径里的正则。带正则的 replace 是测到最慢的一项。
- 预期字节码比压缩后的包更大,为应用体积留出预算。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.