【问题标题】:LuaJIT 2 optimization guideLuaJIT 2 优化指南
【发布时间】:2011-11-02 07:29:54
【问题描述】:

我正在寻找关于如何优化 LuaJIT 2 的 Lua 代码的良好指南。它应该关注 LJ2 的细节,比如如何检测哪些跟踪正在编译,哪些没有编译等。

有什么建议吗?收集 Lua ML 帖子的链接可以作为答案(在此处总结这些链接的奖励积分。)

更新:我已将标题文本从“分析”指南更改为“优化”指南,因为这样更有意义。

【问题讨论】:

  • 我也很高兴有一些关于如何编写对 LuaJIT2 友好的代码的提示(使用本地闭包,使用元表等)。

标签: lua profiling


【解决方案1】:

更新

Mike 最近为 LuaJIT 创建并发布了一个出色的轻量级分析器,您可以在 here 找到它。

更新

wiki 在这方面增加了几页,尤其是 this one,其中详细介绍了原始答案中未提及的一些额外内容,并且基于 Mike 的 mailing list post


LuaJIT 最近推出了自己的 wikimailing list,伴随着这样的事情而来的是更多关于加速 LuaJIT 代码的宝石。

现在 wiki 很薄(但一直在寻找添加到它的人),然而,最近添加的一个很棒的页面是 a list of NYI functions。 NYI 函数会导致 JIT 退出并回退到解释器,因此很明显应该在 hotpath 上尽可能避免 NYI 函数,尤其是在循环中。

邮件列表中一些感兴趣的主题:

并且只是重复下面所说的内容(因为它很有帮助),-jv 是性能调整的最佳工具,它也应该是您进行故障排除时的第一站。


原答案

我怀疑您实际上会发现很多,主要是因为 LJ2 仍处于测试阶段,因此大多数配置文件都是天真的完成的,因为没有针对 LJ2 特定事物(如跟踪记录器)的调试挂钩。

从好的方面来说,新的 FFI 模块确实允许直接调用高分辨率计时器(或分析 API,如 VTune/CodeAnalyst),您可以通过这种方式进行分析,但更多需要扩展 LJ2 JIT 核心,这不应该太难了,因为代码很清楚并且有注释。


一个跟踪记录器命令行参数(取自here):

-jv 和 -jdump 命令是用 Lua 编写的扩展模块。他们 主要用于调试 JIT 编译器本身。为一个 它们的选项和输出格式的描述,请阅读 源代码开头的注释块。它们可以在 源码分发的lib目录或安装在jit下 目录。默认情况下,这是 /usr/local/share/luajit-2.0.0-beta8/jit 在 POSIX 系统上。

这意味着您可以使用命令中的模块代码来形成 LuaJIT 2 的分析模块的基础。


更新

随着问题的更新,这变得更容易回答了。那么让我们从源头开始吧,LuaJIT.org:

在手动优化代码之前,最好检查一下 JIT 的优化调优资源:

编译

Running页面我们可以看到设置JIT参数的所有选项,为了优化,我们关注-O选项。 Mike 立即告诉我们启用所有优化对性能的影响最小,因此请确保在 -O3(现在是默认设置)中运行,因此对我们来说真正有价值的唯一选项是 JIT 和 Trace 阈值。

这些选项非常特定于您正在编写的代码,因此除了默认值之外没有通用的“最佳设置”,但不用说,如果您的代码有很多循环,请尝试循环展开并计时执行时间(但如果您正在寻找冷启动性能,则在每次运行之间刷新缓存)。

-jv 还有助于避免知道 issues/'fallbacks' 会导致 JIT 退出。

除了FFI tutorial 中的一些小花絮之外,该网站本身并没有提供太多关于如何编写更好或更优化的代码:

函数缓存

函数的缓存在 Lua 中是一个很好的性能提升器,但在 LuaJIT 中不太重要,因为 JIT 自己完成了大部分这些优化,需要注意的是 FFI C 的缓存函数不好,最好缓存它们所在的命名空间。

页面示例:

不好:

local funca, funcb = ffi.C.funcb, ffi.C.funcb -- Not helpful!
local function foo(x, n)
  for i=1,n do funcb(funca(x, i), 1) end
end

好:

local C = ffi.C          -- Instead use this!
local function foo(x, n)
  for i=1,n do C.funcb(C.funca(x, i), 1) end
end

FFI 性能问题

Status 部分详细介绍了降低代码性能的各种构造和操作(主要是因为它们未编译,而是使用 VM 回退)。

现在我们转到所有 LuaJIT gem 的 源,Lua mailing list

  • Avoiding C Calls and NYI Lua calls in loops:如果您希望 LJ2 跟踪器启动并提供有用的反馈,您需要避免跟踪编译器无法执行的 NYI(尚未实现)函数或 C 调用。因此,如果您有任何可以导入 lua 并在循环中使用的小型 C 调用,请导入它们,最坏的情况它们可能比 C 编译器实现“慢 6%”,最好的情况是更快。

    李>
  • Use linear arrays over ipairs:根据 Mike 的说法,与其他方法相比,pairs/next 总是会更慢(其中还有一个关于跟踪器符号缓存的小知识)。

  • Avoid nested loops:每个嵌套级别都需要额外的一次跟踪,并且优化程度会稍低,特别是避免迭代次数较少的内部循环。

  • You can use 0-base arrays:Mike 在这里说,与标准 Lua 不同,LuaJIT 对基于 0 的数组没有性能损失。

  • Declare locals in the most inner scope as possible: 没有真正的解释为什么,但 IIRC 这与 SSA 活力分析有关。还包含一些关于如何避免过多本地人的有趣信息(这会破坏活力分析)。

  • Avoid lots of tiny loops:这会扰乱展开的启发式算法,并会减慢代码速度。

小花絮:

分析工具可用于普通 Lua,但是,有一个较新的项目与 LuaJIT 正式兼容(我怀疑它会考虑任何 LuaJIT 功能)luatrace。 Lua wiki 在optimization tips 上也有一个普通 Lua 页面,需要在 LuaJIT 下测试它们的有效性(这些优化中的大部分可能已经在内部执行),但是,LuaJIT 仍然使用默认 GC,这就离开了作为手动优化收益仍然可以很大的一个领域(直到 Mike 添加了他在这里和那里提到的自定义 GC)。

LuaJIT 的源代码包含一些用于调整 JIT 内部的设置,但是,这些设置需要大量测试来调整它们以适应特定代码, 事实上,完全避免它们可能会更好,尤其是对于那些 不熟悉 JIT 的内部结构。

【讨论】:

  • 好吧,至于“在这上面找点东西”。我知道。但我记得 Mike Pall 向 Lua ML 发布了一些实际信息。此外,Lua ML 上的一些人显然正在使用 LJ2 进行分析......我有点期待有人咬紧牙关并在这里写下一个简短的摘要 :-) 抱歉我很懒,但是 200 代表的赏金应该可以弥补这一点有点。
  • 至于跟踪记录器的东西,有 -jv -jdump 神奇的键,它确实打印了一些有用的信息。
  • @Alexander:IIRC 那些“配置文件”(lua-list.2524044.n2.nabble.com/… 这个?)其中只有 scimark 基准:luajit.org/download/scimark.lua 和倾销,Mike actualyl 说这些是用于调试目的(见更新的答案)
  • 不,不仅如此。我记得至少讨论过某种与数学相关的软件……我还记得 Mike 发布了一些图片,取自他的私人分析器……
  • @Alexander:那么最好的办法是给 Mike 发电子邮件,询问他是否愿意将其添加到 git 存储库中(如果它还没有隐藏在其中),他非常擅长回复邮件和邮件列表。
【解决方案2】:

我过去使用过ProFi,发现它非常有用!

【讨论】:

    【解决方案3】:

    不是您想要的,但我已经对 jit.* 跟踪工具进行了一些逆向工程。下面的内容有点粗糙、不准确、可能会发生变化并且非常不完整。我很快就会在luatrace 中开始使用它。这些函数在几个 -j 库文件中使用。 dump.lua 可能是一个不错的起点。

    jit.attach

    您可以使用jit.attach 将回调附加到许多编译器事件。可以调用回调:

    • 当函数被编译为字节码(“bc”)时;
    • 当跟踪记录开始或停止时(“trace”);
    • 正在记录轨迹(“记录”);
    • 或当跟踪通过侧出口(“texit”)退出时。

    使用jit.attach(callback, "event") 设置回调并使用jit.attach(callback) 清除相同的回调

    传递给回调的参数取决于所报告的事件:

    • “bc”:callback(func)func 是刚刚录制的函数。
    • “跟踪”:callback(what, tr, func, pc, otr, oex)
      • what 是对跟踪事件的描述:“flush”、“start”、“stop”、“abort”。适用于所有活动。
      • tr 是跟踪号。不可用于冲洗。
      • func 是被跟踪的函数。可用于启动和中止。
      • pc 是程序计数器 - 正在记录的函数的字节码编号(如果这是一个 Lua 函数)。可用于启动和中止。
      • otr start:父跟踪号,如果这是边跟踪,中止:中止代码(整数)?
      • oexstart:父trace的退出号,abort:中止原因(字符串)
    • “记录”:callback(tr, func, pc, depth)。第一个参数与跟踪启动相同。 depth 是当前字节码的内联深度。
    • “texit”:callback(tr, ex, ngpr, nfpr)
      • tr 和之前一样是跟踪号
      • ex 是出口号
      • ngprnfpr 是在出口处活动的通用和浮点寄存器的数量。

    jit.util.funcinfo(func, pc)

    当从 jit.attach 回调传递 funcpc 时, jit.util.funcinfo 返回有关函数的信息表, 很像debug.getinfo

    表格的字段是:

    • linedefined:至于debug.getinfo
    • lastlinedefined:至于debug.getinfo
    • params: 函数接受的参数个数
    • stackslots: 函数局部变量使用的栈槽数
    • upvalues: 函数使用的上值数
    • bytecodes: 编译函数的字节码数
    • gcconsts: ??
    • nconsts: ??
    • currentline:至于debug.getinfo
    • isvararg: 如果函数是可变参数函数`
    • source:至于debug.getinfo
    • loc:描述源和当前行的字符串,如 ":"
    • ffid:函数的快速函数id(如果是1)。在这种情况下,只有上面的 upvalues 和下面的 addr 有效
    • addr:函数的地址(如果不是Lua函数)。如果是 C 函数而不是快速函数,则只有上面的 upvalues 有效

    【讨论】:

      猜你喜欢
      • 2013-11-06
      • 2013-11-12
      • 1970-01-01
      • 2010-09-16
      • 2011-10-01
      • 2010-12-05
      • 1970-01-01
      • 2012-06-12
      • 2011-10-13
      相关资源
      最近更新 更多