295 字节机器码,跑赢 LuaJIT

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)
  • 本文全部数据于同机同会话现场取得,方法如第二节所述;不同机器的绝对值会不同,但相对梯队与结论应当稳定复现
相关推荐
要开心吖ZSH2 小时前
《通过SeaTunnel 把 MySQL 的数据搬进 Doris》番外篇:容量、切分与速度——Doris 能扛多少,比 MySQL 快多少
mysql·性能优化·doris·分区·分表·分桶
yunwei372 小时前
eBPF 实例教程:BPF 动态指针处理可变长度数据
linux·后端·性能优化
打工仔折腾 AI3 小时前
260+异构同步链路实战:如何用语义化自动翻译破解CDC DDL同步痛点
人工智能·后端·python·自然语言处理·性能优化·langchain·机器翻译
Sayai4 小时前
Java 进程 Unable to create new native thread 排查实战:Linux 线程数五层限制与 JVM -Xss 调优
java·linux·运维·jvm·性能优化
打工仔折腾 AI5 小时前
让时序预测真正参与决策:TimechoAI 工程化落地与管线设计实践
人工智能·后端·python·算法·机器学习·性能优化
可乐ea6 小时前
微调提速 6 倍:Qwen 27B 从 7 小时 44 分到 1 小时 17 分
性能优化·模型微调·训练加速·fsdp2·数据打包
李纲明17 小时前
WordPress 图片拖慢首屏与磁盘:媒体库体积、缩略图爆炸与懒加载排查
性能优化·wordpress·webp·图片优化·媒体库
赵大仁19 小时前
官网性能优化实战:LCP 3.2s → 2.1s、CLS 0.107 → 0,以及被我推翻的 6 个判断
前端·性能优化·cls·lcp·core web vitals
CCblandy1 天前
一致性哈希到底省了多少搬迁?本机实测 3 轮:取模 6→7 搬 85.8%,V=128 只搬 14.3%,虚拟节点太少会被倾斜骗
后端·python·云原生·性能优化·架构