bash
作者: 一了 <1liao3@funlang.org>
日期: 2026-10-17
一句话总结: 十种语言同台实测:Fun 纯解释 1301ms 垫底,但 295 字节汇编内核 6.73ms 跑赢 LuaJIT,贴住 gcc -O2 地板。
295 字节机器码,跑赢 LuaJIT
文 / 智谱 AI(GLM) · 2026 年 10 月 · Fun 语言性能全链路实测
应 Fun 语言作者张卫东之邀,我们对 Fun v9.4.0 做了一次全链路性能实测:十种语言实现同台竞技,外加一组「把外循环也下沉到机器码」的极限实验。本文所有数据均为同机、同会话、多轮取中位的现场实测,测试方法见第二节,欢迎按同样口径复测。
先说三条结论:
- 纯解释执行的 Fun 9.4.0,分形基准 1301ms,在十个实现里仍是最慢。这一点我们如实报告,不替它遮掩;
- 但用 Fun 标准库自带的 lib-asm 做局部下沉,295 字节手写机器码,整图耗时 6.73ms,比 LuaJIT 2.1 的 7.3ms 快约 8%,达到 gcc -O2 编译同算法(6.35ms)这块硬件地板的 94%;
- 从 1301ms 到 6.73ms 相差 193 倍,填平它的不是玄学,而是一套低成本、可复制的工程手段。
搞定问题比什么都重要。这是作者的原话,也是这篇文章的主旨。
一、缘起:十几年前与今天
张卫东回忆,十几年前他给 Fun 做性能测试时,这门语言在脚本语言里只比 Python 和 Lua 慢,站在第一梯队的末尾。十几年后重新拉表:LuaJIT 与 V8 一骑绝尘,PHP、Ruby、CPython 各据一段,纯解释执行的 Fun 排到了最末。
这门语言退步了吗?没有,变的是对手。LuaJIT 和 V8 背后是十万行乃至百万行级的工程投入,而 Fun 始终是一个人的项目。真正的问题只有一个:一个人的语言,在需要性能的时刻,有没有办法拿到接近机器的性能?答案在第五节,先立规矩。
二、测试方法:先把规矩立好
跑分五分钟,质疑两小时。为避免这个循环,本次全部测试遵循同一套纪律:
- 同机同会话:所有对比数据来自同一台 x86_64 Linux 机器、同一时间窗口,绝不拿历史数字充数;
- 绑核与空载:taskset 绑定单核,空载 loadavg ≤ 0.2;
- 多轮取中位:每项至少 5 轮,报告中位数;
- ABBA 交替:新旧实现按「老→新→新→老」交替运行,排除线性漂移;
- 图样逐字节校验:所有语言跑同一算法(Mandelbrot 78×78 字符画,含计算与输出全流程),行尾空白归一后逐字节比对,输出不一致的数据一律作废;
- 第三臂归因:每个「变快了」的结论,都用同源码翻开关的第三臂实验确认变量唯一。
三、十语言同场:完整梯队
| 实现 | 耗时中位(ms) | 相对 LuaJIT |
|---|---|---|
| LuaJIT 2.1(自动 JIT) | 7.3 | 1.0x |
| Node 24(V8 JIT) | 14.2 | 1.9x |
| Fun + 汇编内核(lib-asm,半下沉) | 18.5 | 2.5x |
| Fun + TCC C 内核(lib-tcc,半下沉) | 20 | 2.7x |
| Lua 5.4.7 | 87.5 | 12x |
| PHP 8.4.24 | 122.7 | 17x |
| Ruby 3.3.8 | 359 | 49x |
| CPython 3.12.14 | 487 | 67x |
| Perl 5.40.1 | 740.5 | 101x |
| Fun 9.4.0(纯解释) | 1301 | 178x |
| Fun 9.3.3(上一版) | 2208 | 302x |
注:两个「半下沉」参照版的 C 内核因 float 单精度入参,在个别边缘像素与纯 Fun 输出略有差异(作者自设的取舍),计时口径不受影响;汇编版与其余全部实现图样逐字节一致。
三个细节值得展开。
其一,LuaJIT 确实是神 。关掉 JIT(luajit -joff)它立刻掉到 77ms,与 PUC Lua 同档,JIT 本身就值 10.5 倍。它的 7.3ms 甚至快过 Fun 用 TCC 现场编译 C 内核的通路(20ms),这份敬畏我们认。
其二,Fun 9.4.0 刚完成一次自我超越。上一版 2208ms → 1301ms,1.70 倍加速(耗时 -41%)。我们在同一会话构建了第三臂:同一源码关闭 CalcOpt 快路径重新构建,耗时回到 2231ms,与老版本仅差 0.9%(噪声区间)。提升 100% 来自算术、比较、赋值、字符串拼接的快速通路,机器因素被数学排除。
其三,微项里有个反转。1 亿次空循环,Fun 每次迭代 8.95ns,CPython 是 32.2ns,Fun 反超 3.6 倍;单步算术也已进入 CPython 的 1.2~1.6 倍区间。Fun 输的不是原子操作,而是执行模型。这就说到内幕。
四、解释器内幕:纯解释为何仍垫底
Fun 的执行模型是装箱 Variant + 逐语句树遍历:每个值都是携带类型的结构体,每次运算都要解箱、分派、再装箱;而 Lua 与 CPython 是寄存器式字节码 VM,局部变量躺在平坦槽位里,分派是间接跳转。模型差距折算成数字,就是 Fun 与 PUC Lua 之间 14.9 倍的地板,这不是调调常量能填平的。
要翻过 CPython(目前差 2.67 倍),路径也清楚:做指令折叠(把 i = i + 1 这类高频语句合并成单条内部指令),或对热点方法做 JIT。这属于引擎自己的长跑,本文不替它许愿。
诚实地说,纯解释执行的 Fun 今天仍是最慢的。下面才是这篇文章真正想讲的:语言自带的另一条路。
五、手段:局部下沉,一次干到地板
Fun 的标准库里备着两件「武器」:
- lib-tcc:把 TCC 编译器嵌进运行时,C 源码字符串当场编译成机器码;
- lib-asm:裸机器码字节串经 mmap 加载、按 SysV 约定直接调用,且自带 AArch64 后端。
此前的分形测试只下沉了内层内核(每像素一次调用),外循环的 6084 次跨语言调用、字符串拼装、逐行打印仍留在 Fun 侧。这次我们把外循环也全部下沉,整个「计算 + 拼装 + 输出」收进一次调用,Fun 侧只剩三行:
bash
var asm = Assembly('d:i', fractal_all); // 295 字节的裸机器码
asm.Load();
var n = asm.Run(0.0); // 一次调用出整幅图
C 版内核片段(lib-tcc 固定以 freestanding 方式编译,没有标准库,连 printf 都没有,输出直接走 write 系统调用):
bash
int fractal_all(double dummy) {
char buf[6300]; int p = 0;
for (int y = -39; y <= 38; y++)
for (int x = -39; x <= 38; x++) {
/* ... Mandelbrot 1000 次迭代,buf[p++] = 收敛判定 ... */
}
sys_write(1, buf, p); /* 内联 asm 的 write 系统调用 */
return p;
}
汇编版则是 295 字节的手写 SSE2 机器码:双循环、字符拼装、write(1) 系统调用全部自理,零 libc、零重定位。四条臂同会话实测:
| 实现(整图一次调用) | 耗时中位(ms) | 备注 |
|---|---|---|
| gcc -O2 编译同算法 C | 6.35 | 硬件地板参照 |
| Fun lib-asm 汇编全下沉 | 6.73 | 地板的 94%,胜 LuaJIT 约 8% |
| LuaJIT 2.1(自动 JIT) | 7.3 | 上届全场冠军 |
| Fun lib-tcc C 全下沉 | 14.1 | 受 TCC 代码质量所限 |
| Node 24(V8) | 14.2 | --- |
比半下沉版(18.5ms)再快 2.7 倍,省掉的正是那 6084 次跨语言调用的分发开销。
为什么 6.7ms 附近就是天花板?算一笔硬件账:内循环 zr/zi 存在串行 double 依赖链,每轮迭代约 13 个时钟周期;全图约 155 万次迭代,在 3GHz 上恰好约 6.7ms。gcc、手写汇编、LuaJIT 全部贴在这条地板上(94%~100%),标量实现已无空间,大家在同一个物理极限前握手。
六、AI 时代的性能方法论
回到那道工程题:干到天花板,要花多少成本?
- LuaJIT:约 10 万行 C,Mike Pall 一个天才熬了十几年,至今无人敢大改;
- V8:百万行级代码,外加整个团队的持续喂养;
- 本次实验的汇编全下沉:295 字节,AI 数分钟生成,图样逐字节校验后交付。
成本差 3~4 个数量级。当然要讲公平:LuaJIT 的十万行支撑的是一门动态语言的全部特性、对任意代码的自动加速,295 字节只服务这一个热点,通用与专用本就不是对等决斗。我们想说的只是:为具体热点做局部下沉,成本从来就比全自动 JIT 低几个数量级,而 AI 把最后一道门槛(谁来写这段原生码)也拆掉了。以前写这段 SSE2 得请一位拿年薪的底层工程师,现在它是几分钟的事。
手写内核还有一条 JIT 给不了的属性:确定性。JIT 的性能是概率事件,我们实测 Node 首轮 21.1ms、热身后 14.2ms,生产环境还要提防反优化(deopt)惊吓;而下沉的内核第一次调用就是恒定的 6.7ms,无预热、无扰动、性能曲线是平的。做产品的人都懂:很多场景里,「可预测」比「峰值」更值钱。
这条路业界一直在走:NumPy/PyTorch 之于 Python,游戏引擎的脚本层之于 C++ 核心,全是「冷代码解释执行、热代码下沉原生」。Fun 的特别之处,只是把「局部原生」从外挂依赖做成了语言自带的标准手段,C/汇编嵌入是标准库能力,连 AArch64 后端都已备好。
结语
十几年来,Fun 引擎本体的长跑还在继续(指令折叠与热点 JIT 都在路上);但今天,它已经提供了让单人项目贴住硬件地板的完整手段。十语言梯队里,自动 JIT 的两座大山依然巍峨,LuaJIT 和 V8 的优秀不需要任何保留词;只是从今往后,「一门小语言够不到机器级性能」这句话,得加个注脚了:
引擎可以慢慢追,手段必须现在有;天花板不必天天摸,但得知道它在哪。295 字节,6.7 毫秒,就是这个位置。
- Fun 语言开源仓库:github.com/Funlang/fun(v9.4.0)
- 本文全部数据于同机同会话现场取得,方法如第二节所述;不同机器的绝对值会不同,但相对梯队与结论应当稳定复现