除了调试,反射的另外一个常见用法是用于调优,即程序使用资源的行为分析。对于时间相关的调优,最好使用 C
接口,因为每次钩子调用函数开销太大从而可能导致测试结果无效。不过,对于计数性质的调优, Lua
代码就可以做的很好。本篇笔记将开发一个原始的性能调优工具来列出程序执行的每个函数的调用次数。
性能调优工具的主要数据结构是两个表,其中一个表将函数和他们的调用计数关联起来,另一个表关联函数和函数名。这两个表的索引都是函数自身:
local Counters = {} local Names = {}
我们可以在性能分析完成后再获取函数的名称,但是如果能在一个函数F处于活跃状态时获取其名称可能得到更好的结果。这是因为:在函数 F
处于活动状态时, Lua
语言可以通过分析正在调用 F
的代码来找出函数 F
的名称。
现在,我们定义一个钩子函数,该钩子函数的任务是获取当前正在被调用的函数,并递增相应的计数器,再收集函数名,如下所示:
local function hook() local f = debug.getinfo(1, "f").func local count = Counts[f] if count == nil then Counters[f] = 1 Names[f] = debug.getinfo(1, "Sn") else Counters[f] = count +1 end end
接下来,运行带有钩子的程序。假设我们要分析的程序位于一个文件中,且用户通过参数把改文件名传递给性能分析器,如下:
$ lua profiler main-prog
这样,性能分析器就可以从 arg[1]
中得到文件名、设置钩子并运行文件:
local f = assert(loadfile(arg[1])) debug.sethook(hook, "c") -- 设置call事件的钩子 f() -- 运行主程序 debug.sethook() -- 关闭钩子
最后一步是显示结果,如下示例的函数 getname
为每个函数生成一个函数名:
function getname (func) local n = Names[func] if n.what == "C" then return n.name end local lc = string.format("[%s]:%d", n.short_src, n.linedefined) if n.what ~= "main" and n.namewhat ~= "" then return string.format("%s (%s)", lc, n.name) else return lc end end
由于 Lua
语言中的函数名并不是特别确定,所以我们给每个函数再加上位置信息,以 file:line
这样的形式给出。如果一个函数没有名称,那么就只使用它的位置。如果函数是 C
函数,那么就只使用它的名称(因为没有位置)。在上述函数定义后,我们输出每个函数及其计数器的值:
for func, count in pairs(Counters) do print(getname(func), count) end
对于这个性能分析器,还有几个地方可以改进。例如,可以输出进行排序、打印更易读的函数名和美化输出格式等。不过,这个原始的性能分析器本身已经是可用了。