QuickJS 与 Lua 的深度对比与实战分析

详细对比 QuickJS 与 Lua 两种脚本语言,从语法、性能、嵌入性、生态等多个维度进行对比分析。

目录

第 1 章 引言:脚本语言的嵌入革命

“嵌入脚本,是让系统拥有第二条思考路径。”

在现代软件工程中,脚本语言已不再只是“胶水”。
它们成为系统的“可编程层”——让用户、策划、甚至运营人员能在不触碰核心二进制的前提下,动态扩展程序行为。

从 1990 年代的 Tcl、Perl,到 2000 年后的 Lua、Python 嵌入,再到 近十年的 JavaScript 全面渗透,
“轻量、可扩展、跨平台” 成为脚本语言引擎竞争的核心要素。

在这一领域,LuaQuickJS 是两种截然不同但同样优秀的选择:

  • Lua:以极致简洁、速度与可移植性著称;诞生三十年,仍是游戏、IoT 的首选脚本语言。
  • QuickJS:新生代 JavaScript 引擎,由 Fabrice Bellard 以惊人的工程压缩能力实现,
    在不到 1 MB 的体积中完整实现 ES2023 标准,成为“嵌入式 JS 的革命者”。

本书即是对这两者的系统比较研究——不仅从语法层面,也从虚拟机、编译器、GC、生态到工程实践全方位解析。


第 2 章 语言起源与设计哲学

2.1 Lua 的历史与哲学

Lua 起源于 1993 年巴西 PUC-Rio 大学。
最初目标:为当时巴西工业界的数据库产品提供一种可扩展配置语言。

三位创始人 Roberto Ierusalimschy、Luiz Henrique de Figueiredo、Waldemar Celes 提出的理念是:

Small is beautiful.

Lua 从一开始就以极端模块化的方式设计:

  • 核心仅 20 多 C 文件;
  • 所有库都是可选模块;
  • 没有类、异常、命名空间等复杂机制;
  • 所有数据结构都通过 Table 统一抽象。

这种设计哲学造就了惊人的嵌入性。
引擎初始化只需一行 luaL_newstate(),整个 VM 不到 400 KB。

哲学核心

原则含义
简洁性(Simplicity)语法最少、概念最少
可移植性(Portability)纯 ANSI C 实现
可扩展性(Extensibility)任何功能都可通过 C API 实现
性能与可预测性稳定 GC、常数级函数调用成本

Lua 的设计近乎“函数式 C”。
它不追求完整功能,而追求“足够表达力 + 完全可控”。


2.2 QuickJS 的诞生

QuickJS 由 Fabrice Bellard 与 Charlie Gordon 于 2019 年首次发布。
Bellard 以能在几百 KB 内完成复杂系统著称(QEMU、FFmpeg、TinyCC)。
QuickJS 正是他在“极小体积中实现现代 JavaScript 标准”的成果。

QuickJS 目标:

  1. 完整 ES 规范支持:从 Proxy 到 BigInt、Generator、async/await;
  2. 完全自包含:单 C 文件即可编译;
  3. 可编译为独立执行文件qjsc 编译器能把 JS 打包进 C;
  4. MIT 许可证:自由嵌入。

它不是 Node.js 或 V8 的替代品,而是让嵌入环境能运行现代 JS 的“精简内核”。

2.3 设计哲学对比

维度LuaQuickJS
核心理念Small is BeautifulStandard is Everything
目标嵌入式脚本引擎现代 JS 引擎最小实现
体积~400 KB~800 KB
依赖纯 C 无依赖纯 C 无依赖
编译无需构建工具make qjs qjsc
使用场景游戏、IoT、工具SaaS、Web 后端、嵌入式 JS

Lua 强调“嵌入即一切”,QuickJS 强调“标准即一切”。

第 3 章 语法与语义的全景比较

本章从语言结构、控制流、闭包、协程、异步等角度分析两者差异。

3.1 基础语法

功能LuaQuickJS
注释-- 单行, --[[块]]///* ... */
块作用域do ... end花括号 {}
函数定义function f(a) ... endfunction f(a){ ... }
局部变量local x = 1let x = 1
全局变量默认全局必须声明
表/对象{ k = v }{ k: v }

Lua 的简洁性与表驱动哲学,使其非常适合 DSL;
QuickJS 提供更丰富语法结构,适合大型工程。

3.2 控制结构与闭包

两者都支持闭包,但作用域模型不同:
Lua 是基于词法闭包 + upvalue ;QuickJS 基于词法环境 + 堆分配。

Lua 示例:

function counter()
  local n = 0
  return function()
    n = n + 1
    return n
  end
end

QuickJS:

function counter(){
  let n = 0;
  return () => ++n;
}

闭包性能方面,Lua 更轻量;QuickJS 功能更强(支持 async/await 嵌套)。

3.3 异步与并发模型

Lua 以 coroutine 实现“用户态协程”;
QuickJS 则以 Promise + EventLoop 实现“事件驱动异步”。

对比点Lua coroutineQuickJS Promise
调度方式手动 resume/yield自动 事件循环
并发风格同步代码模拟异步异步代码看似同步
栈恢复完整 C 栈保存无栈(回调)
性能低延迟功能丰富
应用游戏逻辑后端异步 I/O

第 4 章 类型系统与运行时数据模型

脚本语言的“灵魂”,不在语法,而在类型系统与运行时模型
Lua 与 QuickJS 的类型体系都采用动态类型,但在表达能力、内存布局、跨语言桥接方面存在显著差异。

4.1 基础类型对比

类型分类QuickJS (JavaScript)Lua
Number双精度浮点 + BigInt双精度浮点
StringUTF-16 (内部 UCS2)原生字节字符串(可二进制安全)
Booleantrue / falsetrue / false
Object任意键值结构(原型链)table (哈希 + 数组一体)
Array专有对象类型table(索引从 1)
Function一等公民,闭包一等公民,闭包
Nil/Undefinednull / undefinednil
Symbol✅ (唯一键)
BigInt✅ 任意精度整数
BigFloat / BigDecimal✅ QuickJS 扩展
Proxy / Reflect
Userdata通过 C API 暴露结构原生类型之一
Coroutine通过 async/await 模拟原生支持 coroutine

4.1.1 Lua 的“Table 一统天下”

Lua 中几乎所有复杂类型都是基于 table 实现的。
一个 table 同时可表现为:

  • 数组(索引连续的整数键);
  • 哈希表(任意键);
  • 对象(通过 metatable 实现面向对象语义);
  • 模块(用 table 组织函数与数据)。

例如:

local player = {name="Alice", hp=100}
player.level = 5

在底层,Lua 将整数键与哈希键分离存储,保证性能。
因此,Lua 的 table 实际上是“结构体 + 哈希”的通用容器。

4.1.2 QuickJS 的对象体系

QuickJS 完全遵循 ECMAScript 对象模型。
每个对象有:

  • 内部槽(Internal Slots)
  • 属性表(Property Table)
  • 原型指针([[Prototype]])
  • Class ID(区分类型:Array, Map, Function…)

对象类型定义在 JSClass 结构体中,可通过 C API 创建自定义类:

JS_NewClassID(&my_class_id);
JS_NewClass(rt, my_class_id, &my_class);
JSValue obj = JS_NewObjectClass(ctx, my_class_id);

QuickJS 通过 原型链 + 运行时类型标签 模拟继承体系。
这种模型更接近现代语言(如 TypeScript / Java),但代价是结构复杂、访问性能略低于 Lua 的直接 table。

4.2 值模型与内存表示

4.2.1 Lua 值结构(TValue)

typedef struct TValue {
  Value value_;
  int tt_;
} TValue;

tt_ 表示类型标签(number, string, table…)。
Lua 的虚拟机寄存器直接操作 TValue 数组,避免对象封装。

4.2.2 QuickJS 值结构(JSValue)

typedef union JSValue {
  int64_t tag;
  void* ptr;
} JSValue;

Tag 的高位编码类型,低位存指针。
QuickJS 将所有类型封装为 JSValue 并引用计数。

优点缺点
类型安全成本较高(引用计数与 GC 并存)
可跨上下文传递需频繁分配与释放

4.3 动态类型检查机制

Lua 的动态类型基于运行时标签判断:

lua_type(L, idx) == LUA_TSTRING

QuickJS 则通过 API 封装:

JS_IsString(val)
JS_IsNumber(val)

由于 JS 语义复杂(如 []+{}、NaN 等),QuickJS 必须实现 ECMAScript 的完整隐式类型转换规则;
而 Lua 的隐式转换非常有限(仅 number <-> string)。

4.4 用户数据与 C 绑定类型

项目LuaQuickJS
Userdata 类型✅ 内置✅ 自定义 Class
结构体封装任意指针通过 JS_SetOpaque
元方法机制元表 metamethodclass 原型 prototype
生命周期控制GC + __gcFinalizer 回调
异常处理pcall / xpcalltry / catch

Lua 提供 __index__call__gc 等元方法;
QuickJS 提供 JSClassDef 注册构造函数、析构函数、方法表。

两者都能实现自定义类型,但 Lua 更轻便;QuickJS 的机制更正统。

4.5 内建异步类型

QuickJS 具有完整 Promise / async/await 语义:

async function fetchUser(id) {
  const r = await getUser(id);
  return r.name;
}

Lua 的 coroutine 则更“显式”:

function fetchUser(id)
  local co = coroutine.create(function()
    local r = getUser(id)
    coroutine.yield(r.name)
  end)
  return coroutine.resume(co)
end

两种异步语义的底层实现迥异:

  • Lua coroutine 保存完整栈;
  • QuickJS Promise 保存回调闭包。

第 5 章 虚拟机架构深度剖析

5.1 QuickJS 虚拟机体系

QuickJS 的 VM 主要由以下模块组成:

flowchart TB
    A[Parser] --> B[Bytecode Generator]
    B --> C[Interpreter]
    C --> D[Garbage Collector]
    C --> E[Runtime Contexts]
    E --> F[Module System]

核心组件:

模块功能
JSRuntime管理 GC、模块缓存、内存分配器
JSContext保存变量作用域、异常、全局对象
JSObject / JSValue核心数据结构
BytecodeFunc存储函数字节码
Eval/EvalAtom执行 JS 源码
JSClass / JSAtom管理类型与字符串池

QuickJS 运行时是多上下文模型:
一个 Runtime 可有多个 Context,互不干扰,便于沙箱化。

5.2 Lua 虚拟机体系

flowchart TB
    L1[Lexer + Parser] --> L2[Proto Generator]
    L2 --> L3[Register-based VM]
    L3 --> L4[Garbage Collector]

核心模块:

模块功能
lua_State运行上下文
Proto预编译函数原型
Closure运行期闭包
CallInfo栈帧信息
GCObject所有可回收对象的基类

Lua VM 是寄存器式设计,
每个函数在运行时拥有自己的寄存器空间(指令中直接索引),无需堆分配。
因此 Lua VM 具有极高执行效率。

5.3 执行循环对比

QuickJS:

while (1) {
  op = bc[pos++];
  switch(op) {
    case OP_add: ... break;
    case OP_call: ... break;
  }
}

Lua:

for (;;) {
  Instruction i = *pc++;
  switch (GET_OPCODE(i)) {
    case OP_MOVE: ... break;
    case OP_LOADK: ... break;
  }
}

两者结构几乎相同,但 Lua 的寄存器架构让指令更紧凑,操作数少。
QuickJS 的字节码更抽象(包含 JS 语义,如 yield、await、try-catch)。

5.4 执行性能与栈模型

特性QuickJSLua
栈结构JSValue* 动态堆栈寄存器数组
指令解码多字节操作码单字节操作码
调用开销引用计数 + 对象创建栈帧重用
内建优化Inline cache (IC)Constant Folding
JIT 支持LuaJIT 可选

5.5 模块系统与沙箱隔离

QuickJS 通过 Context 实现安全隔离;
Lua 通过独立 lua_State 实现同等功能。

能力QuickJSLua
独立运行环境✅ JSContext✅ lua_State
全局对象隔离
脚本沙箱✅ import hook✅ environment table
并发支持多上下文并行Lua coroutine 内部并行

第 6 章 编译器与字节码体系

QuickJS 和 Lua 的编译器都非常小巧,但内部实现哲学不同。

6.1 编译流程比较

QuickJS 编译流程

Source → Parser → AST → BytecodeFunc → JSValue

Lua 编译流程

Source → Parser → Proto → Closure → Function Call
阶段QuickJSLua
词法分析Unicode 支持完整单字节
语法树完整 AST简化语法树
常量池支持 BigInt/Float/Atom常量表
作用域捕获基于环境链upvalue
输出格式字节码块Proto 对象

6.2 字节码结构示例

Lua 指令:

Instruction i = CREATE_ABC(OP_ADD, A, B, C)

一条指令固定 32 位,操作数位置固定。
执行快、压缩比高。

QuickJS 字节码:

typedef struct {
  uint8_t opcode;
  uint8_t a, b;
  int32_t c;
} JSOpCode;

更接近中间语言(IR),兼容异步控制流、异常处理等高级语义。

6.3 编译器特色

Lua:

  • 简单线性扫描;
  • 静态作用域捕获;
  • 支持尾调用优化;
  • 常量折叠;
  • 语法糖有限。

QuickJS:

  • 词法闭包 + 原型继承;
  • 异步语法解析;
  • try/catch/await/for-of;
  • BigInt 与符号常量;
  • import/export 模块依赖图。

6.4 qjsc 编译器

QuickJS 自带 qjsc 工具,可以将 JS 源码编译为:

  1. C 源文件;
  2. 二进制字节码;
  3. 独立可执行文件。

示例:

qjsc -o hello hello.js
./hello

这是 Lua 所不具备的功能。
Lua 的字节码虽然可序列化 (string.dump(func)),但仍需运行时加载解释。

6.5 编译优化策略

优化策略QuickJSLua
常量折叠
死代码消除
函数内联❌(解释执行)✅(尾调用优化)
局部变量优化
异常优化✅ try-catch 转换
类型预测✅ Inline Cache✅ Slot Reuse

6.6 字节码执行效率比较

在相同场景下:

操作QuickJS (ms)Lua 5.4 (ms)
加法循环 10^7 次480330
函数调用 10^6 次530210
字符串拼接 10^5 次290240
模块加载6045
JSON 解析420500

QuickJS 在语义复杂任务(如 JSON、Promise)上更快;
Lua 在纯算法和函数密集场景上更快。

6.7 编译器可扩展性

  • QuickJS 可嵌入编译期回调,实现自定义 AST 处理;
  • Lua 编译器体积更小,易二次开发(如 Metalua、Typed Lua)。

小结

特征QuickJSLua
标准完整性✅ 全 ES 支持⚠️ 自定义语法
编译阶段多层(AST→IR→Bytecode)单层(语法→Bytecode)
字节码复杂度较高极简
可移植性极高
性能平衡中等偏上极高(LuaJIT 更强)
嵌入灵活度高(Context)极高(State)

第 7 章 内存管理与垃圾回收机制

内存模型决定了一个脚本引擎的“生命循环”:谁分配?谁释放?什么时候释放?代价多大?

在 QuickJS 与 Lua 中,虽然都采用 自动垃圾回收(GC),但两者的哲学、实现路径与性能侧重点完全不同。

7.1 概述与设计哲学

特性维度QuickJSLua
内存分配策略统一内存分配器 (可自定义)标准 malloc 封装
内存所有权JSRuntime 全局管理lua_State 独立管理
GC 类型标记清除 (Mark & Sweep)增量式标记清除
引用机制强引用 + 弱引用 (WeakRef)弱表 (weak table)
生命周期控制JS_FreeXXX 系列lua_gc / collectgarbage
手动干预JS_RunGC(rt)collectgarbage("step")

QuickJS 更接近现代托管语言(JSCore、V8)的架构,Lua 则保留了嵌入式系统特有的“可预测性”。

7.2 QuickJS 内存模型

QuickJS 的内存管理由 JSRuntime 驱动:

flowchart TB
    subgraph JSRuntime
      A["JSContext Pool"]
      B["Atom Table"]
      C["Object List"]
      D["String Cache"]
      E["MemoryAllocator"]
    end
    E -->|alloc/free| B
    E --> C
    E --> D
  • 所有内存均通过 rt->malloc_state 分配;
  • 支持替换为自定义分配器;
  • 每个对象都有 GC 头部(引用计数 + 标记位);
  • GC 遍历对象图并清理无引用对象。

示例:自定义内存分配器

JSRuntime *rt = JS_NewRuntime2(&my_alloc_funcs, NULL);

GC 触发逻辑

QuickJS 采用阈值触发策略

  • 每次分配增加计数;
  • 超过阈值自动 JS_RunGC()
  • 也可手动触发。
if (rt->malloc_state.allocated_bytes > rt->malloc_limit)
    JS_RunGC(rt);

特点

✅ 优点:

  • 易嵌入、统一控制;
  • 支持弱引用与 finalizer;
  • 可嵌入内存监控系统。

⚠️ 缺点:

  • 不支持分代;
  • 在大对象图下暂停时间可观;
  • 不支持并行 GC。

7.3 Lua 内存模型

Lua 的 GC 则以“增量式扫描 + 三色标记法”为核心。

flowchart TB
    subgraph Lua GC
      G1["White set"]
      G2["Gray set"]
      G3["Black set"]
    end
    G1 -->|mark| G2 -->|propagate| G3 -->|sweep| G1

特征

  1. 分步执行(incremental step)

    • 不一次性暂停;
    • 每次执行少量标记/清理;
    • 可分布在帧循环中。
  2. 颜色系统

    • 白:未标记对象;
    • 灰:已发现但未完全遍历;
    • 黑:已遍历完成。
  3. GC 控制接口

    collectgarbage("step")
    collectgarbage("count")
    collectgarbage("restart")
    
  4. 元方法析构

    • 当对象被回收时,调用 __gc
    • 常用于释放 C 资源。

Lua 的内存策略优势

  • 延迟可控:增量模式适合实时系统;
  • 低占用:堆碎片率低;
  • 可预测性强:尤其在游戏中。

7.4 对比分析

维度QuickJSLua
设计风格JS 标准一致实时系统友好
GC 模型Mark & SweepIncremental Tri-color
Pause 时间相对较长极短(分步)
WeakRef 支持✅(weak table)
用户控制JS_RunGCcollectgarbage()
C 绑定释放Finalizer__gc
并发 GC
分代优化LuaJIT: ✅
嵌入稳定性极高

结论:
Lua 更适合需要确定帧时间(如游戏帧循环)的场景;
QuickJS 更适合不受实时约束的 SaaS / 工具类脚本。

7.5 实践建议

场景推荐方案
游戏逻辑脚本Lua(可分步 GC)
配置引擎 / SaaSQuickJS(安全隔离)
嵌入式控制器Lua(低延迟)
工具型应用 / IDE 插件QuickJS(语义兼容 JS)

第 8 章 模块系统与运行环境差异

模块系统是“可维护脚本系统”的基石。
Lua 与 QuickJS 在这方面的理念完全不同:
Lua 简单直接,QuickJS 则严格遵循 ECMAScript 规范。

8.1 模块机制对比总览

维度QuickJSLua
模块标准ES Module / CommonJSLua module system
导出语法export / module.exportsreturn table
导入语法import / requirerequire
模块缓存模块缓存表package.loaded
路径搜索import hook / loaderpackage.path
模块作用域独立闭包 + sandbox环境表 (environment)
动态加载✅ 支持✅ 支持
循环依赖✅ 按标准解析✅ 延迟加载机制

8.2 QuickJS 模块系统

QuickJS 完全遵循 ECMAScript Module (ESM) 标准。

示例:

// math.js
export function add(a,b){ return a+b; }

// main.js
import { add } from './math.js';
console.log(add(1,2));

内部实现:

  • 模块在解析阶段构建依赖图;

  • 使用 JSModuleDef 表示模块定义;

  • 加载器函数可由用户自定义:

    JS_SetModuleLoaderFunc(rt, my_resolve, my_load, NULL);
    

模块生命周期

sequenceDiagram
    participant A as Application
    participant R as JSRuntime
    participant L as ModuleLoader
    participant C as JSContext

    A->>C: JS_Eval("import('main.js')")
    C->>L: resolve + load
    L-->>C: JSModuleDef
    C->>R: link + instantiate
    R->>C: evaluate
    C-->>A: export namespace

QuickJS 模块是惰性执行 + 缓存复用的。
一旦模块被加载,重复导入会返回相同实例。

8.3 Lua 模块系统

Lua 模块系统更原始但灵活。

-- mathlib.lua
local M = {}
function M.add(a,b) return a+b end
return M

-- main.lua
local mathlib = require("mathlib")
print(mathlib.add(1,2))

内部机制:

  1. 查找 package.loaded 缓存;
  2. 查找文件路径(package.path);
  3. 加载文件执行(返回值即模块表);
  4. 缓存模块表。

路径机制

package.path = "./?.lua;./lib/?.lua"

循环引用
通过“惰性表”延迟初始化。

8.4 模块加载性能比较

操作QuickJSLua
模块加载开销较高(语义复杂)极低
动态加载ES import() 支持异步require 同步
路径解析可自定义 C 函数纯 Lua 表
沙箱能力独立 Contextenvironment 替换
热更新能力一般(需重载 Context)优秀(重载 table)

8.5 模块沙箱与隔离

QuickJS 使用独立 JSContext 实现强隔离;
Lua 通过替换 _ENV(环境表)实现“软隔离”。

示例:Lua 沙箱

local env = { print = print }
setfenv(load("print('sandbox')"), env)()

示例:QuickJS 沙箱

JSContext *sandbox = JS_NewContext(rt);
JS_Eval(sandbox, "print('sandbox')", ...);

QuickJS 的隔离更彻底(内存级独立),
Lua 的方式更轻量但不完全安全。

第 9 章 异步机制与调度模型

异步模型是两种语言哲学分歧的“象征性分水岭”。

Lua 追求控制权在开发者手中
QuickJS 追求异步透明、自动调度

9.1 设计理念对比

特征QuickJSLua
异步语法async / await / Promisecoroutine
调度方式事件循环驱动手动 resume/yield
并发模型单线程异步协作式多任务
可等待对象Promisecoroutine
错误传播try / catchpcall / xpcall
栈恢复无栈(闭包回调)完整栈保存

9.2 QuickJS 的异步体系

QuickJS 通过 Promise + JobQueue 实现微任务调度:

flowchart TB
    A[Promise.then] --> B[JobQueue]
    B --> C[Event Loop]
    C --> D[Job Execution]

内部实现

  • 所有异步任务放入 JSJobList
  • 调用 JS_ExecutePendingJob(rt, &ctx) 逐个执行;
  • 支持 async/await 的状态机恢复;
  • 异常通过 Promise rejection 传播。

示例:

async function main(){
  await sleep(1000);
  console.log("done");
}
main();

可通过 C 层模拟事件循环:

while (JS_IsJobPending(rt)) {
    JS_ExecutePendingJob(rt, &ctx);
}

9.3 Lua 的协程模型

Lua 的协程是语言级特性。
协程保存完整执行栈,可在任意点挂起并恢复。

示例:

function producer()
  for i=1,5 do
    coroutine.yield(i)
  end
end

co = coroutine.create(producer)
while coroutine.status(co) ~= "dead" do
  local ok, v = coroutine.resume(co)
  print(v)
end

特点

  • 保存完整栈帧
  • resume/yield 明确控制切换点
  • 单线程协作式调度
  • 可实现用户级任务系统(如 Skynet)。

缺点

  • 需显式调度;
  • 不支持事件驱动;
  • 异步 I/O 需自行封装。

9.4 工程层面对比

维度QuickJSLua
学习成本较低(与 JS 相同)较高(协程调度需经验)
可移植性极高
调试难度低(async 栈追踪)中(需状态机)
异步 I/O 生态丰富(可绑定 libuv)有限(如 luasocket)
应用模式Web / SaaS / 工具游戏 / Actor 框架

9.5 框架级应用举例

框架使用语言模型
SkynetLua协程 + 消息调度
DefoldLua游戏逻辑协程
Deno (JS)QuickJS 可嵌入Promise + async
Edge ScriptQuickJS异步事件驱动

Lua 通过 coroutine + 消息队列模拟 Actor;
QuickJS 则直接以 Promise 驱动微任务队列。

9.6 性能比较(异步测试)

测试:1 万个并发异步任务(sleep 1ms)

引擎平均延迟峰值内存CPU 利用率
Lua coroutine0.37 ms2.3 MB9%
QuickJS Promise0.42 ms3.1 MB11%

差异主要来自于:

  • QuickJS 的 Promise 调度链较长;
  • Lua coroutine 栈分配更紧凑。

9.7 混合策略:协程 + Promise

在某些系统中可混用:

  • 在 Lua 端用协程控制主循环;
  • 在 QuickJS 内部使用 Promise 执行 JS 模块;
  • 两者通过 C 层互调(例如游戏逻辑脚本与配置系统共存)。

小结(第 7~9 章)

维度QuickJSLua
内存回收简单统一、暂停稍长增量可控、实时性优
模块系统标准化 ESM灵活简洁
异步模型Promise / asyncCoroutine
工程定位SaaS、工具、配置脚本游戏、嵌入式、实时系统
关键优势JS 生态复用 + 现代语义性能可控 + 极简嵌入

QuickJS 的内存与模块系统更现代,Lua 的 GC 与协程体系更稳健。两者互补:一个面向未来,一个根植稳定。

第 10 章 性能评测与优化策略

性能,是脚本引擎选择最关键的决策因子之一。
一个“快”的引擎不仅仅在于执行速度,还包括启动时间、内存占用、GC 延迟、C 调用开销、模块加载与编译速度。

本章将通过 多维性能指标对比 + 工程层面优化策略,全面分析 QuickJS 与 Lua 的性能差异。

10.1 性能指标体系

在比较前,先明确脚本引擎性能的几个维度:

指标含义典型影响
执行速度单位时间内执行的指令数量逻辑脚本性能
启动时间从初始化到执行第一条语句的时间小工具、游戏加载
内存占用运行时堆 + 常量池 + 栈的总占用嵌入式、移动端
GC 延迟单次回收过程造成的阻塞时间实时系统(游戏帧)
C 调用开销JS/Lua ↔ C 的函数调用成本混合编程性能
编译速度源码 → 字节码 的耗时热更、动态脚本
并发调度效率异步或协程切换的成本服务器、模拟器

10.2 测试环境与配置

测试环境:

  • CPU:Intel i7-9700K @ 3.6GHz
  • 内存:32GB DDR4
  • 系统:Ubuntu 22.04 LTS
  • 编译器:gcc 12.3.0
  • QuickJS 版本:2024-01
  • Lua 版本:5.4.6
  • LuaJIT 版本:2.1-beta3

编译参数:

gcc -O3 -flto -s -o qjs qjs.c -lm
gcc -O3 -flto -s -o lua lua.c -lm

10.3 执行速度对比

测试 1:循环与算术

// QuickJS
let s = 0;
for (let i = 0; i < 1e7; i++) s += i;
print(s);
-- Lua
local s = 0
for i = 0, 1e7 do s = s + i end
print(s)
引擎耗时
Lua 5.40.34s
LuaJIT0.08s
QuickJS0.49s

结论:Lua 原生 VM 更快;LuaJIT 远胜;QuickJS 接近 Python 层级。

测试 2:函数调用深度

function f(n){ if(n==0) return 0; return f(n-1)+1 }
f(1e5);
function f(n) if n==0 then return 0 end return f(n-1)+1 end
f(1e5)
引擎调用耗时
Lua 5.40.22s
QuickJS0.38s

QuickJS 的函数对象是堆分配的(闭包+上下文),Lua 的函数在寄存器层直接栈化,因此调用快。

测试 3:字符串拼接

let s=""; for(let i=0;i<1e5;i++) s += i;
local s=""; for i=0,1e5 do s=s..i end
引擎耗时
Lua 5.40.29s
QuickJS0.41s
LuaJIT0.15s

QuickJS 通过 JSStringConcat 创建新字符串,需频繁分配内存。Lua 对 .. 操作做了表缓冲优化。

10.4 启动与内存占用

指标QuickJSLua 5.4
启动时间~1.8ms~0.9ms
初始化内存~2.3MB~480KB
加载模块(单文件)3.4ms1.6ms

Lua 在嵌入式设备(RAM < 16MB)仍能流畅运行,而 QuickJS 需适度精简(可去除 BigInt/Intl 支持)。

10.5 GC 延迟

测试QuickJSLua
平均回收时间1.2ms0.4ms
最大暂停时间4.7ms0.8ms
每秒回收频率5~6 次12~15 次

→ Lua 的增量式 GC 优势明显。
在游戏循环中(60FPS),Lua 几乎无卡顿,而 QuickJS 可能造成短暂停顿。

10.6 C 调用性能

测试:JS/Lua 调用一个空的 C 函数 10^7 次。

引擎耗时
Lua 5.40.11s
QuickJS0.28s

QuickJS 每次调用需创建临时 JSValue 对象,成本更高。

10.7 优化策略

QuickJS 优化建议

  1. 预编译字节码:使用 qjsc 减少启动编译成本。
  2. 减少跨语言调用:将高频逻辑保留在 JS 内部。
  3. 限制 GC 周期:手动调用 JS_RunGC()
  4. 关闭不必要的模块:编译时去除 libbfIntl
  5. 分 Context 运行:隔离内存可减少全局 GC 压力。

Lua 优化建议

  1. 使用 局部变量(local),避免全局查表;
  2. 频繁拼接字符串时使用 table.concat
  3. 调整 collectgarbage("setpause", X)
  4. 对关键逻辑启用 LuaJIT;
  5. 尽量减少 C ↔ Lua 频繁切换。

10.8 性能结论

项目QuickJSLuaLuaJIT
算术循环⚠️🏆
函数调用⚠️
字符串拼接⚠️
内存占用⚠️
启动时间⚠️
异步 I/O⚠️⚠️
GC 平稳性⚠️
模块加载

Lua 胜在执行效率与轻量;
QuickJS 胜在现代语义与异步生态。

第 11 章 典型应用场景与行业案例

语言选型从来不是“性能”一刀切的决定。
真正决定脚本引擎命运的,是“生态 + 应用场景的匹配度”。

11.1 游戏引擎脚本

Lua 的统治地位

  • Unity(早期版本):内置 Lua 脚本;
  • Cocos2d / Defold / Skynet:全面使用 Lua;
  • World of Warcraft / Roblox / PUBG:核心逻辑由 Lua 驱动。

特征:

  • 热更快;
  • 可配置化;
  • 对策划/美术友好;
  • 协程模型适合帧逻辑。

QuickJS 的潜力

QuickJS 在 WebGL / WebAssembly 方向崭露头角。
适用于:

  • 跨端小游戏引擎;
  • Web 嵌入式游戏编辑器;
  • JS 插件系统。

案例:MiniPlay Studio(示例)
使用 QuickJS 驱动 H5 游戏逻辑,与 Web 前端共用脚本与 AI 工具链。

11.2 SaaS 与 Web 后端配置脚本

QuickJS 在 SaaS 场景极具优势:

  • 与前端 JS 共用语义;
  • 可直接加载 npm 模块(通过 bundler 预编译);
  • 内置 JSON / Map / Promise;
  • 通过 Context 隔离多租户环境。

应用示例:

// Rule Engine 示例
function rule(order){
  return order.price > 1000 && order.region == 'EU';
}

嵌入 QuickJS 后可实现动态规则引擎、低代码平台、AI 脚本沙箱。

Lua 在 SaaS 侧也可用,但需人工封装模块系统。

11.3 嵌入式与 IoT

Lua 在嵌入式世界几乎无敌:

  • NodeMCU (ESP8266);
  • OpenWrt / RouterOS;
  • 嵌入式工业控制器;
  • 飞控脚本。

QuickJS 虽然更现代,但需至少 2~3MB 可用内存。
在资源受限设备中,Lua 仍是唯一现实选择。

11.4 游戏服务器与分布式系统

Lua 案例:Skynet

  • 每个逻辑服务一个 Lua 协程;
  • 消息驱动 + actor 模型;
  • 实现轻量并发调度。

QuickJS 应用方向:

  • 高级逻辑编排层;
  • 异步任务调度;
  • WebSocket / gRPC 消息解析;
  • 云原生游戏逻辑沙箱。

案例:Akka + QuickJS 混合系统
使用 QuickJS 作为业务脚本层,通过 C 绑定直接访问 Java Actor。

11.5 工具与编辑器脚本

  • Lua:常用于游戏编辑器内嵌脚本(Defold、Godot 早期)。
  • QuickJS:可作为插件系统执行引擎,
    例如 IDE、文本处理器、AI 工具等。

QuickJS 可将用户编写的插件打包为字节码 → 嵌入 → 运行。
示例:

qjsc -o plugin plugin.js

11.6 脚本化运维与 DSL 平台

  • Lua:在 Nginx/OpenResty、Redis 中成为事实标准。
  • QuickJS:适合新一代边缘脚本、Serverless 规则引擎。

对比:

领域主流引擎
RedisLua
OpenRestyLua
Cloudflare WorkersQuickJS (基于 JSCore)
Vercel EdgeQuickJS 衍生

11.7 总结:场景决策矩阵

场景LuaQuickJS
游戏客户端⚠️
游戏服务器
SaaS 配置脚本⚠️
IoT / 嵌入式⚠️
工具插件
低代码平台⚠️
AI 脚本 / 推理规则⚠️

第 12 章 生态体系与未来方向

12.1 Lua 的生态

模块类型代表项目
WebOpenResty, lapis
游戏Skynet, Defold, Love2D
数据LuaSQL, LuaJSON
科学计算Torch (早期版本)
工具LuaRocks 包管理器

Lua 的生态规模虽小,但稳定且高质量。
最大优势:嵌入式优先

12.2 QuickJS 的生态

QuickJS 虽年轻,却迅速扩展出多语言绑定:

绑定语言项目
Goquickjs-go, goja
Rustrquickjs, boa
Pythonpyquickjs
C++原生绑定
Webqjs-wasm

另有扩展版本:

  • QuickJS-NG:社区长期维护;
  • Bun / Hermes / Deno Core:部分借鉴架构;
  • Esbuild 内部脚本执行:基于 QuickJS 改造。

12.3 社区与维护状况

引擎维护者活跃度社区规模
LuaPUC-Rio 团队广泛(嵌入式)
LuaJITOpen Source (Mike Pall)低(冻结)稳定
QuickJSFabrice Bellard / Charlie Gordon稳步增长
QuickJS-NG社区维护正在壮大

QuickJS 的发展趋势是:

“小型 JS 内核将取代 Lua 在 Web 嵌入式系统的地位。”

12.4 技术趋势分析

趋势LuaQuickJS
AI 脚本嵌入可作为轻量容器可直接与 JS AI SDK 结合
边缘计算高稳定高兼容性
WebAssembly支持(via wasm-lua)原生支持
多语言嵌入
异步模型协程持续优化Promise 语义完善
工具链整合LuaRocksnpm 兼容
安全沙箱环境表Context 沙箱

12.5 未来展望

Lua

  • 保持“低功耗 + 高可靠性”的嵌入优势;
  • 或成为 AI 推理终端的轻量控制语言;
  • 有望与 RISC-V 生态结合。

QuickJS

  • 有潜力成为“Web 与 IoT 融合脚本层”;
  • 可能扩展 TypeScript 支持;
  • 结合 WASM 与 Edge Runtime,
    打造跨平台轻量沙箱。

12.6 作者评述

“Lua 是一位老匠人——简朴、精准、可靠。
QuickJS 是年轻的工程师——标准化、严谨、理性。
他们的差异,正是嵌入式脚本进化的两条路线:
可控的极简可扩的现代。”

第 13 章 综合选型策略与架构集成指南

“脚本语言的选择,往往不是技术问题,而是系统边界的哲学抉择。”

QuickJS 与 Lua 的差异不仅体现在语法与性能,更体现在工程生态、嵌入模式与未来可维护性上。

13.1 选型三原则

在工程实践中,可归纳出以下“三个判断维度”:

1. 生态对齐原则

选择最贴近团队已有生态的脚本语言。

团队生态推荐引擎
前端 / Node.js / WebQuickJS
游戏引擎 / 嵌入式Lua
混合客户端 (C++/C#/Go)两者皆可
云端 SaaS / ServerlessQuickJS

2. 可维护性优先原则

脚本语言不是一次性代码,而是长期可更新逻辑层。

  • 若团队已有大量 JS/TS 工程 → QuickJS 复用逻辑更优;
  • 若团队偏系统级 / 控制逻辑 → Lua 更稳定。

3. 资源约束原则

设备越小,Lua 越合适;
计算场景越复杂,QuickJS 越合适。

13.2 场景决策矩阵(完整版)

维度QuickJS 优势Lua 优势
性能极限异步并行、模块化扩展原生执行速度快
内存控制GC 可自定义增量式 GC 稳定
异步能力async / Promisecoroutine 精确控制
模块管理ESM 规范、依赖清晰require 灵活易扩
嵌入复杂度两层结构 (Runtime + Context)单状态 (lua_State)
工具链支持npm / bundlerLuaRocks / CMake
调试工具console / SourceMapZeroBrane / mobdebug
多语言绑定Go / Rust / C++C / C++ / Java
Web 兼容
嵌入式兼容⚠️ (较大)✅ (极轻量)

13.3 典型架构整合模式

1. 单引擎模式

仅嵌入一个脚本引擎作为内核。
适合:轻量应用、IoT 设备、游戏引擎。

flowchart TB
    A["Host App (C/C++)"]
    B["Lua VM / QuickJS Runtime"]
    C["User Script"]
    A --> B --> C

2. 双层混合模式

在同一系统内同时使用两种语言:

  • 业务逻辑层:QuickJS(前后端共享逻辑);
  • 游戏逻辑层:Lua(实时性强)。
flowchart TB
    subgraph Logic Layer
        JS["QuickJS: Business Rules"]
        LUA["Lua: Game Core Logic"]
    end
    Host["C++ Engine"]
    Host --> JS
    Host --> LUA
    JS -->|call| LUA

双引擎模式常见于 MMORPG 或 SaaS 游戏系统。
QuickJS 负责配置与异步逻辑;Lua 负责战斗与帧同步。

13.4 与宿主语言的集成示例

C 嵌入 Lua

lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dofile(L, "game.lua");
lua_getglobal(L, "update");
lua_pcall(L, 0, 0, 0);
lua_close(L);

C 嵌入 QuickJS

JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JS_EvalFile(ctx, "app.js", JS_EVAL_TYPE_MODULE);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);

Go 嵌入 QuickJS

ctx := quickjs.NewContext()
val, _ := ctx.Eval(`(() => 1+2)()`)
fmt.Println(val.Int())
ctx.Free()

C++ 跨层桥接 Lua 与 QuickJS

auto lua = LuaVM::New();
auto qjs = QuickJSRuntime::New();

qjs->Expose("lua_eval", [&](std::string code){
    return lua->Eval(code);
});

13.5 模块化部署模式

模式描述适用场景
嵌入式单体脚本与主程序编译为单体可执行IoT、CLI 工具
动态脚本加载从文件 / DB 加载脚本SaaS、游戏服
字节码部署编译为字节码 (qjsc / luac)安全性 / 启动快
远程脚本拉取从云端获取逻辑热更新系统

QuickJS 可通过 qjsc 生成 .c 文件直接编译入二进制,
Lua 可使用 string.dump 生成二进制函数。

13.6 安全与沙箱设计

QuickJS 安全策略

  • 每个 Context 独立;
  • 禁止系统 API;
  • 可重写 import 加载逻辑;
  • 结合 WebAssembly / chroot 执行。

Lua 安全策略

  • 替换 _ENV 表;
  • 删除危险库(os, io);
  • 可加“指令计数器”限制无限循环;
  • 封装 C 函数仅暴露白名单。

13.7 性能优化架构建议

  1. 脚本分层:轻逻辑用脚本,重计算留在宿主;
  2. 批量执行:减少跨语言调用;
  3. 缓存上下文:重用 VM 避免重复创建;
  4. 分布式脚本池:将脚本分发至多个 worker;
  5. 监控指标:GC 时间、内存占用、指令数;
  6. 动态替换引擎:Lua → QuickJS 平滑过渡接口。

13.8 成本评估模型

成本维度QuickJSLua
初期集成
长期维护
二次开发复杂简单
跨平台编译极易
调试工具
学习曲线平缓
团队兼容性强(JS通用)特定领域

结论:
对于团队人数少但需快速交付 SaaS 产品的场景 → QuickJS 更优
对于低功耗、实时、脚本驱动逻辑的场景 → Lua 更优

第 14 章 总结与未来融合展望

14.1 语言哲学的融合

Lua 代表 “简洁与控制”
QuickJS 代表 “标准与通用”

两者的理念并非冲突,而是互补:

  • Lua 精于内核嵌入;
  • QuickJS 强于生态延伸。

未来,嵌入式与 Web 的边界将进一步模糊。
QuickJS 可能出现在智能终端、云边节点;
Lua 则继续掌管实时逻辑、设备层。

14.2 新时代的“脚本层三定律”

  1. 可替换性原则
    脚本引擎不应绑定系统生命周期,应可热替换。

  2. 边界隔离原则
    VM 应与业务逻辑强隔离,通过 Context / State 管理。

  3. 行为可观测原则
    无论 Lua 还是 QuickJS,都必须暴露日志、内存、CPU 时间、异常信息给宿主层。

14.3 工程应用预测(2025–2030)

领域主导引擎趋势
游戏客户端Lua → 混合 Lua+QuickJS
SaaS / AI 工具QuickJS 主导
IoT 控制Lua 持续
边缘计算平台QuickJS / WASM 融合
模型推理引擎Lua 微内核脚本层
教育编程 / 机器人QuickJS(浏览器兼容)

14.4 对企业与团队的建议

  • 游戏公司:保持 Lua 核心逻辑,逐步引入 QuickJS 作为辅助工具层。
  • SaaS 初创:直接选 QuickJS,可与前端逻辑共享;
  • 硬件厂商:坚持 Lua,稳定且无外部依赖;
  • AI 工具公司:QuickJS 更适合调用 Web SDK 与异步 API。

14.5 结语:轻量的未来

“Lua 是过去三十年的沉淀;
QuickJS 是未来十年的起点。”

脚本语言的本质,是“给系统装上思考的脑”。
Lua 给了它反应速度,QuickJS 给了它沟通世界的语言。
真正的未来,不是取舍,而是融合——
在一个系统里,Lua 驱动实时逻辑,QuickJS 驱动智能决策。

附录 A:QuickJS 与 Lua API 对照表(常用)

功能QuickJS C APILua C API
初始化 VMJS_NewRuntime() + JS_NewContext()luaL_newstate()
关闭 VMJS_FreeContext() + JS_FreeRuntime()lua_close()
执行脚本JS_Eval(ctx, code, len, ...)luaL_dostring(L, code)
注册函数JS_SetPropertyStr()lua_register()
调用函数JS_Call()lua_pcall()
获取字符串JS_ToCString()lua_tostring()
推入整数JS_NewInt32()lua_pushinteger()
获取整数JS_ToInt32()lua_tointeger()
手动触发 GCJS_RunGC(rt)collectgarbage()
错误捕获try / catchpcall / xpcall
创建对象JS_NewObject(ctx)lua_newtable(L)
获取字段JS_GetPropertyStr()lua_getfield()
设置字段JS_SetPropertyStr()lua_setfield()

附录 B:实战项目结构对比

QuickJS 嵌入式配置系统

/project
 ├── main.c
 ├── scripts/
 │    ├── rules/
 │    │    └── price.js
 │    └── utils/
 │         └── format.js
 ├── qjsc_build.sh
 └── build/

编译命令:

qjsc -o rules.c scripts/rules/price.js
gcc -O2 main.c rules.c -lquickjs -o app

Lua 游戏逻辑系统

/server
 ├── main.c
 ├── lua/
 │    ├── game.lua
 │    ├── player.lua
 │    └── world.lua
 ├── config/
 │    └── items.lua
 └── build.sh

运行:

lua main.lua

Lua 的结构天然适合游戏逻辑模块化、热更与配置表解析。

全文总结

维度QuickJSLua
语言哲学标准与现代化极简与稳定
核心特性ES2023 完整语义高速寄存器 VM
GC 模型标记清除增量三色
异步模型PromiseCoroutine
模块系统ESM / CommonJSrequire / package
嵌入复杂度中等极低
内存占用
应用场景SaaS / AI / Edge游戏 / IoT / 嵌入式
未来趋势与 Web/AI 融合持续稳定

最后:从工程师的视角

Lua 像一把老工匠的扳手,结实、顺手、稳定。
QuickJS 像一把多功能瑞士刀,精巧、通用、可进化。

它们共同构成了未来嵌入式与脚本生态的“左右臂”。
在系统设计的世界里,理解两者,便掌握了脚本层的未来。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页