LuaJIT 官方性能优化指南和注解(UDD_William,博客园)

ABSTRACT

2016-10 博客园文章(2018-01 收藏)。对 LuaJIT 作者 Mike Pall 官方数值计算性能指南的注解版,共 12 条:不止讲怎么做,更解释 trace compiler 的假设-守护(guard)机制,说明为什么不可预测分支、table 滥用、非 jit 操作会让性能断崖式下跌。

核心要点

  • LuaJIT 性能飘忽的根因:trace compiler 先跑字节码、对热点做 profile,然后按「大胆假设 + guard 守护」生成机器码;假设频繁被打破就反复跳出编译代码,性能雪崩。
  • 第 1 条(作者置顶):减少不可预测分支——某分支命中率 >95% 才可预测;可用 math.min/max、bitop 绕过 if-else。解释器模式(iOS)对分支不敏感,此条主要针对 JIT 模式。
  • FFI 数据结构是性能利器:Vector3 用 ffi struct 实现比 lua table 内存省 10 倍、运算快 8 倍——table 本质是哈希表,ffi 让 jit 直接读内存。但 iOS 只能解释执行,ffi 反而更慢,该优化基本仅限安卓核心代码。
  • 用 FFI 调 C 函数:有函数原型信息,jit 可生成无缝调用机器码;否则(2.1.0 之前)直接导致整段 jit 失败。
  • 循环用数值 for 或 ipairs;for k,v in pairs 直到 2.1.0beta2 都不支持 jit。
  • 循环展开由 lj_opt_loop 运行时处理,嵌套循环可能展开过度被取消,需实测平衡。
  • 函数尽量 local;跨模块常用函数用 local 缓存(local ms = math.sin)——省一次表查找 + 一次全局表查找。
  • 别自造消息分发机制(if-elseif 链);用 table/metatable 查找,可参与 jit 优化。
  • 不要替 jit 做手工优化:local 变量尽量进寄存器,但数量过多(armv7 上尤甚)会让 jit 放弃编译——控制作用域内存活 local 数。
  • 引用类型别名会阻止子表达式优化(x 和 a 可能是同一个表);值类型才有此优化。
  • 减少存活临时变量:Vector3.__add 连写 a+b+c 会爆量产生临时对象,权衡性能与可读性;可用 do…end 提前结束变量生命周期。
  • 避开 NYI(Not Yet Implemented)操作:pairs、print(用 io.write)、字符串连接打日志(用 log("haha %s", x) 惰性格式化)、非尾部 table.insert。

关键实体与概念

LuaJIT、trace compiler、guard 守护、FFI、NYI、jit/解释器模式、upvalue、循环展开、寄存器分配、元表查找

关联概念

来源回溯

  • 原始文件:raw/ip/wechat_articles/luajit官方性能优化指南和注解 - UDD_William - 博客园.md(原文 2016-10-24,收藏 2018-01-20)

时效性评估

  • 仍有效:trace compiler 的假设-guard 原理、local 缓存、避免 NYI 操作等是 LuaJIT 的持久知识;「先 profile 再优化热点」的方法论通用。
  • 已过时:LuaJIT 项目 2015 年后基本停止功能演进(2.1 beta 悬置多年),文中部分 NYI 限制在后续 beta 有变化;iOS 平台现已普遍用 xlua(官方 Lua 5.3/5.4)或 HybridCLR,LuaJIT 的使用场景大幅收缩——新项目选型时本指南主要适用于存量 tolua/LuaJIT 项目维护。