第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些不同之处。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助支持 Lua 项目


23.3 – 概要

尽管名称如此,但调试库可用于除调试之外的其他任务。此类常见任务是分析。对于带时序的分析,最好使用 C 接口:每个挂钩的 Lua 调用开销过大,通常会使任何度量失效。但是,对于计数分析,Lua 代码可以胜任。在本节中,我们将开发一个基础分析器,它列出程序中每个函数在运行中被调用的次数。

我们程序的主要数据结构是一个将函数与其调用计数器关联起来的表,以及一个将函数与其名称关联起来的表。这些表的索引是函数本身。

    local Counters = {}
    local Names = {}
我们可以在分析后检索名称数据,但请记住,如果我们在函数处于活动状态时获取其名称,则我们会获得更好的结果,因为那时 Lua 可以查看调用该函数的代码以查找其名称。

现在,我们定义挂钩函数。它的工作是获取被调用的函数并递增相应的计数器;它还收集函数名称

    local function hook ()
      local f = debug.getinfo(2, "f").func
      if Counters[f] == nil then    -- first time `f' is called?
        Counters[f] = 1
        Names[f] = debug.getinfo(2, "Sn")
      else  -- only increment the counter
        Counters[f] = Counters[f] + 1
      end
    end
下一步是使用此挂钩运行程序。我们将假设程序的主块位于一个文件中,并且用户将此文件名作为参数提供给分析器
    prompt> lua profiler main-prog
使用此方案,我们在 arg[1] 中获取文件名,打开挂钩,然后运行该文件
    local f = assert(loadfile(arg[1]))
    debug.sethook(hook, "c")  -- turn on the hook
    f()   -- run the main program
    debug.sethook()   -- turn off the hook
最后一步是显示结果。下一个函数为函数生成一个名称。由于 Lua 中的函数名称非常不确定,因此我们向每个函数添加其位置,表示为一对文件:行。如果函数没有名称,则我们只使用其位置。如果函数是 C 函数,则我们只使用其名称(它没有位置)。
    function getname (func)
      local n = Names[func]
      if n.what == "C" then
        return n.name
      end
      local loc = string.format("[%s]:%s",
                                n.short_src, n.linedefined)
      if n.namewhat ~= "" then
        return string.format("%s (%s)", loc, n.name)
      else
        return string.format("%s", loc)
      end
    end
最后,我们打印每个函数及其计数器
    for func, count in pairs(Counters) do
      print(getname(func), count)
    end

如果我们将分析器应用于我们在 第 10.2 节 中开发的 markov 示例,我们将得到如下结果

    [markov.lua]:4 884723
    write   10000
    [markov.lua]:0 (f)     1
    read    31103
    sub     884722
    [markov.lua]:1 (allwords)      1
    [markov.lua]:20 (prefix)       894723
    find    915824
    [markov.lua]:26 (insert)       884723
    random  10000
    sethook 1
    insert  884723
这意味着第 4 行的匿名函数(这是在 allwords 中定义的迭代器函数)被调用了 884,723 次,write (io.write) 被调用了 10,000 次,依此类推。

您可以对该分析器进行多项改进,例如对输出进行排序、打印更好的函数名称以及改进输出格式。尽管如此,这个基本的分析器本身已经很有用了,并且可以用作更高级工具的基础。