跳到主要内容

ZLua 与 xLua 对比分析

本文档从 架构、调用路径、理论开销 三方面对比 ZLua 与 xLua(Tencent,Unity Il2Cpp 常用方案)。用于技术选型与设计评审。

选型理由(用法、GC、Wrapper 等) 请先读 为什么选择 ZLua;本文侧重 路径级性能推演

说明:

  • 开销数据为 路径级推演 + 经验量级,非仓库内 benchmark。
  • ZLua 对比对象为其 Player / Il2Cpp 设计目标当前 Il2Cpp 实现仍为 MVP(Mono Editor 已具备 v1.0 全量功能,见 项目状态)。
  • 性能对比 仅对完整 Il2Cpp Player 有意义;当前 Player MVP 尚未体现设计目标性能。

1. 定位概览

维度xLuaZLua
成熟度生产级,生态大早期,规范为主
Lua 引擎独立 libxlua(动态库 / P/Invoke)链入 libil2cpp(与 VM 同二进制)
Lua→C# 桥生成 C# Wrap + LuaDLLC++ MethodBridge + methodPointer
C#→LuaC# LuaEnv + 多次 LuaDLL[LuaInvoke] InternalCall + C++ 模板
字段访问多数经生成 Wrap / 属性规范:__index offset 直读(Il2Cpp)
维护成本插件升级,少改 libil2cppfork libil2cpp,Unity 版本需 merge
易用性文档、工具链完善目标「无感」+ 与 C# 语义对齐
浅 ←────────────────────────────────────────→ 深(Il2Cpp 侵入)

反射绑定 / 纯 C# 桥
xLua(libxlua + 生成 Wrap)
★ ZLua 目标
HybridCLR 级 VM 改造

ZLua 与 xLua 同属 高性能原生绑定 路线,而非 HybridCLR 式执行模型改造。


2. 调用路径对比

2.1 Lua → C#:例 Demo.Add(3, 5) / demo:GetX()

xLua

lua_pcall
→ [MonoPInvokeCallback] 生成的 C# Wrap(Il2Cpp AOT 机器码,入口无托管调度)
→ LuaDLL.lua_tointeger / lua_push*(P/Invoke → libxlua)
→ 回到 Wrap
→ 调用目标 C# 方法(Il2Cpp 生成代码)
→ 再经 LuaDLL 写回栈
→ (实例方法)ObjectTranslator:userdata → pool index → object

ZLua

lua_pcall
→ C++ MethodBridge(与 lua 同链接域)
→ lua_tointeger / lua_push*(直接 lua API)
→ method->methodPointer(...)(Il2Cpp 生成体同级)
→ ObjectRegistry:userdata → slot(弱表缓存)

ZLua MVP 示例(MethodBridge.cpp)对 Add(int,int) 即为:lua_tointeger ×2 → methodPointerlua_pushinteger,无 Wrap、无 libxlua 往返。

2.2 C# → Lua:例 [LuaInvoke] / 脚本回调

xLua

C# 业务代码
→ LuaEnv / DelegateBridge(C#)
→ 多次 LuaDLL(getglobal、push、pcall、pop…)
→ libxlua → lua VM

单次调用常见 5–15 次 LuaDLL 跨界。

ZLua

C# [LuaInvoke] → InternalCall(一次进 native)
→ LuaInvokeRuntime::Call(C++ 模板,内联)
→ lua_rawgeti(funcRef) + PushDefault* + lua_pcall + Pop

构建期解析 moduleRef / funcRef,运行时无按名 getglobal

2.3 Lua function → C# Delegate

xLuaZLua
机制C# delegate bridge + translator + LuaDLLLuaMethod + funcRef + closed delegate + C++ DelegateBridge
脚本侧传 function,内部转换隐式 marshal(方法形参,同其他类型)
详见xLua lua_csfunction / DelegateBridge../marshal/function.md §4.0

绑定完成后,两者都要走「C# 调 invoke → 进 Lua pcall」;ZLua 少 C# Wrap / LuaDLL 层,但 string / 重对象 仍占主导。


3. 分项开销(理论量级)

环境假设: ARM64 / x64、Release、热循环、简单 blittable 签名。单位为 纳秒级区间,仅供推演。

成本项xLua (Il2Cpp)ZLua (目标)备注
Lua 进入 C 回调~5–15~5–15同在 lua VM 内
栈读写单次~30–150(经 LuaDLL)~3–15(内联 lua API)主要差距来源
C#↔native 边界~20–80 / 次0 或 IC 1 次xLua 栈操作多次跨界
userdata → 对象指针~20–80~10–40均为池 + 缓存思路
方法分派~0–30(Wrap 常写死)~0–50(map / dispatch)重载 dispatch 两边都可能贵
调用 Il2Cpp 方法体~1–5~1–5同为 methodPointer
int 实例字段常经 Wrap,~150–500+offset 直读 ~15–50字段差距最大
string 往返~200–2000+类似UTF 转换 + GC,瓶颈一致

4. 分场景理论估计

定义:

  • 总耗时倍数 = T_xLua / T_ZLua(越大 ZLua 越快)
  • 互调开销削减 ≈ (T_xLua − T_nova) / (T_xLua − T_函数体),适用于极短函数体

场景 A:热循环 int Add(int a, int b)(静态)

指标估计
xLua 粗算~150–600 ns/次
ZLua 粗算~30–100 ns/次
总耗时约 2–5×(常见 ~3×)
互调开销削减约 70%–90%

场景 B:实例 int GetX()(无参)

指标估计
总耗时约 3–6×
互调开销削减约 75%–90%

场景 C:实例字段 obj.xint

指标估计
xLua__index → Wrap → getter
ZLua__index → C++ offset getter
总耗时约 5–15×
互调开销削减约 85%–95%

场景 D:C# → Lua,void Foo(int) / int Bar(int,int)

指标估计
总耗时约 2–4×
互调开销削减约 60%–85%

参数越多,xLua「每次 push 都跨界」放大越明显。

场景 E:Delegate 回调(已绑定后)

指标估计
总耗时约 2–3×
互调开销削减约 50%–75%

首次绑定、泛型约束、签名未 codegen 时两边都贵。

场景 F:含 string、复杂 object / List

指标估计
总耗时约 1.1–1.8×
互调开销削减约 20%–50%

marshal 与分配占主导,固定边界成本占比下降。

汇总表(Player / 轻量签名)

场景总耗时倍数 (xLua/ZLua)互调开销大约减少
Lua→C# int,int→int2–5×70%–90%
Lua→C# 实例 getter3–6×75%–90%
Lua 读 int 字段5–15×85%–95%
C#→Lua 少参函数2–4×60%–85%
Delegate 回调2–3×50%–75%
重 string / 对象 API1.1–1.8×20%–50%

保守结论(热路径、blittable、高频):

互调固定开销有机会减少 约 50%–90%;端到端常见 快 2–5 倍;字段直读等路径可更高。


5. 为何 ZLua 能更快(根因)

根因说明
消灭 libxlua 往返栈操作不再每次 P/Invoke
消灭生成 C# Wrap 层Lua→C# 在 C++ 一次完成 marshal + methodPointer
C#→Lua 单次 IC非 C# 循环调 LuaDLL
字段 / 无参属性快路径与 Il2Cpp 生成 C++ 同级内存访问
签名复用桥同 HybridCLR 思路,控制代码体积

不是单一「Il2Cpp 黑科技」值 10 倍,而是 减少重复的 native↔managed↔native 折返


6. Il2Cpp 侵入性对比

层级xLuaZLua
插件形态独立 native + 生成 C#嵌入 libil2cpp + 少量 Runtime/GC 挂钩
方法调用Wrap + LuaDLLmethodPointer 直调
DelegatexLua 自有 bridgeSetClosedDelegateInvoke + 生成 bridge
GC一般不改 Boehmnon-blittable struct 可能 hook push_other_roots
升级 Unity主要升 xLua 包merge libil2cpp 补丁(工程债更大)

ZLua 刻意避免运行时伪造完整 MethodInfo(见 ../marshal/function.md),在技巧深度上有所克制。


7. 功能与工程面对比(非性能)

维度xLuaZLua
文档 / 社区建设中
代码生成成熟 XLua GenerateCodegen + Weaver([LuaInvoke]
热更配套大量现成实践需自建
语义模型xLua 自有约定目标贴近 C#(TYPE_SYSTEM_SPEC
Editor 体验与 Player 较一致Mono 反射 / Player 原生 双轨
迁移../community/roadmap.md 迁移指南(规划)

8. 必须打折的因素

8.1 ZLua 实现阶段

当前 MVP 仅覆盖少量 MethodBridge 签名;以下尚未完全落地:

  • 完整 overload dispatch
  • ReadDelegate / DelegateBridges
  • 字段 __index 快路径全覆盖
  • struct handle、GC root

上文倍数为规范落地后的 Player 理论值,非今日 Demo 全状态。

8.2 ZLua 固定成本

  • 首次 EnsureBinding、继承 promotion
  • 运行时重载 dispatch
  • string、non-blittable、泛型方法 inflation

8.3 xLua 可优化空间

  • 生成 Wrap 已避免反射
  • 可对热点减少 LuaDLL 调用次数
  • 架构上仍难消除 libxlua 边界

8.4 帧预算视角

互调即使快 5 倍,若脚本边界只占帧时间 2%,整帧仅省 ~1.6%
务必先 profiling 确认 Lua↔C# 是否为热点(战斗公式、UI 每帧上千次小调用等)。


9. 理论下限参考

同进程嵌入 Lua 调同签名 native 函数的下限约:

lua 回调 + 2×读栈 + 1×写栈 + methodPointer ≈ 20–60 ns

ZLua 热路径 接近 该下限;xLua 因多次 LuaDLL,通常 高一个数量级


10. 建议基准测试(待实测)

相同 Il2Cpp Player 下对比 xLua 与 ZLua:

-- 1. Lua → C# 静态
for i = 1, 1000000 do Demo.Add(1, 2) end

-- 2. Lua → C# 实例方法
for i = 1, 1000000 do o:GetX() end

-- 3. Lua 读字段
for i = 1, 1000000 do local _ = o.x end

-- 4. C# 循环 [LuaInvoke] 空调用 / int 返回

-- 5. C# 持 Action,循环回调 Lua function

记录:ns/callGC Alloc/帧。预期与 §4 趋势一致;实测后在本文件追加「实测数据」一节。


11. 选型建议(简要)

选 xLua选 ZLua(目标态)
现在要上线、要少踩坑性能边界是瓶颈且愿维护 libil2cpp fork
团队已有 xLua 资产希望语义与 C# 统一、极致 Player 性能
不愿改 Unity 引擎层可投入 Codegen + 双运行时测试矩阵

两者 不是 简单替换关系;迁移成本见后续迁移文档。


12. 相关文档

文档内容
为什么选择 ZLua选型理由、用法与 GC 对比
设计规范ZLua 总体目标
../architecture/il2cpp-architecture.mdPlayer 架构、LuaInvoke、MethodBridge
../type-system-spec.md元表、字段快路径
../marshal/function.mdDelegate / 函数 marshal
../marshal/struct.mdstruct 零 GC 路径

文档版本:理论分析稿;实测数据待补充。