文章摘要
这篇文章介绍了如何优化一个简单的AST解释器(用于动态语言Zef),使其性能接近Lua、QuickJS和CPython。作者分享了从零开始的优化技巧,不涉及复杂技术(如JIT、GC或字节码),却实现了16倍的性能提升。
文章总结
如何打造一个快速的动态语言解释器
本文记录了作者如何通过一系列优化手段,将一个简单的AST遍历解释器(为自创的Zef语言设计)性能提升到与Lua、QuickJS和CPython相媲美的过程。
优化背景
大多数关于语言实现性能优化的讨论都集中在已有稳定基础后的工作,如编写JIT编译器或调优垃圾回收器。本文则关注从零开始的情况,介绍了一些易于理解的技术(不涉及SSA、GC、字节码或机器码),最终实现了16倍的性能提升(如果算上不完整的Yolo-C++移植则是67倍)。
评估方法
作者创建了名为ScriptBench1的基准测试套件,包含: - Richards(操作系统调度器) - DeltaBlue(约束求解器) - N-Body(物理模拟) - Splay(二叉树测试)
测试环境为Ubuntu 22.04.5系统,Intel Core Ultra 5 135U处理器,32GB内存。
主要优化步骤
- 直接调用运算符:避免字符串比较分派,实现17.5%加速
- 直接调用RMW运算符:优化如
a += b这类操作,获得3.7%提升 - 避免IntObject检查:简化整数类型判断,提升1%
- 使用符号代替字符串:用指针代替字符串进行哈希查找,提速18%
- 内联关键函数:通过
valueinlines.h实现2.8%加速 - 对象模型重构与内联缓存:这是最大的改动,结合了:
- 新的对象存储模型
- 内联缓存技术
- 监视点机制 实现了4.55倍的性能飞跃
- 参数传递优化:引入专用
Arguments类型,减少分配,提速33% - Getter特化:识别并优化简单的getter方法,提升5.6%
- Setter特化:类似getter优化,再获3.4%提升
- 方法调用内联:关键函数内联带来3.2%提升
- 全局哈希表:优化方法查找,提速15%
- 避免std::optional:解决Fil-C++的分配问题,提升1.7%
- 专用参数类型:针对不同参数数量特化,提速3.8%
- 改进Value慢路径:避免不必要的分配,提速10%
- sqrt特化:直接处理数学函数调用,提升1.6%
- toString优化:优化字符串转换,提速2.7%
- 数组字面量特化:优化常量数组创建,提速8.1%
- callOperator改进:进一步优化慢路径,提速6.5%
- 调整C++编译选项:禁用RTTI等,提升1.8%
最终成果
经过21项优化后,解释器性能提升了16.6倍。当使用Yolo-C++编译时(牺牲内存安全性),性能进一步提升至67倍,超越了CPython和QuickJS,接近Lua的水平。
关键洞见
- 良好的值表示设计是基础
- 内联缓存技术即使在解释器中也非常有效
- 对象模型设计直接影响性能上限
- 减少分配是解释器优化的关键
- 特化常见操作能带来显著收益
这些优化展示了即使不使用JIT编译,通过精心设计的数据结构和算法,也能实现动态语言解释器的高性能。
评论总结
总结评论内容:
- 对项目技术构成的好奇(评论1)
- 关注代码库的语言构成比例:"repo is 99.7% HTML and 0.3% C++"
- 认为这体现了解释器的精简:"A testament to the interpreter's size"
- 对实际应用效果的询问(评论2)
- 直接询问使用体验:"How's your experience with Fil-C been"
- 关注实用价值:"Is it materially useful to you in practice"
- 对功能扩展的建议(评论3)
- 表达对Lua支持的认可:"I see Lua was included"
- 建议增加LuaJIT支持:"wish LuaJIT was as well"
不同观点保持平衡,既有对技术细节的好奇,也有对实际应用的关注,还有对功能扩展的建议。所有评论均未显示评分(None)。