我问了一个关于Lua性能的问题,并询问了回答:
你有没有研究过保持Lua性能高的一般技巧?即知道表创建而不是重用表而不是创建新表,使用'local print = print'等来避免全局访问.
这是一个与Lua模式,提示和技巧略有不同的问题,因为我希望答案能够特别影响性能,并且(如果可能的话)解释为什么性能会受到影响.
每个答案一个提示是理想的.
回应其他一些答案和评论:
确实,作为程序员,您通常应该避免过早优化.但是.对于脚本语言而言,情况并非如此,因为编译器的优化程度不高 - 或根本没有.
因此,无论何时在Lua中编写内容并且经常执行,在时间关键的环境中运行或者可能运行一段时间,知道要避免的事情(并避免它们)是一件好事.
这是我随着时间的推移发现的一个集合.其中一些是我在网上发现的,但是当涉及到互联网时我有一种可疑的性质,我自己测试了所有这些.另外,我在Lua.org上阅读了Lua性能论文.
一些参考:
Lua性能提示
Lua-users.org优化技巧
避免全局变形
这是最常见的提示之一,但再次说明它不会受到伤害.
Globals按名称存储在哈希表中.访问它们意味着您必须访问表索引.虽然Lua有一个非常好的哈希表实现,但它仍然比访问局部变量慢很多.如果必须使用全局变量,将它们的值赋给局部变量,则在第二次变量访问时速度更快.
do x = gFoo + gFoo; end do -- this actually performs better. local lFoo = gFoo; x = lFoo + lFoo; end
(不是那么简单的测试可能会产生不同的结果.例如,local x; for i=1, 1000 do x=i; end
这里for循环头实际上比循环体需要更多的时间,因此分析结果可能会失真.)
Lua在创建时散列所有字符串,这使得比较和在表中使用它们非常快并减少了内存使用,因为所有字符串仅在内部存储一次.但它使字符串创建更加昂贵.
避免过多字符串创建的流行选项是使用表.例如,如果必须组装一个长字符串,请创建一个表,将各个字符串放在那里,然后使用table.concat
它来连接一次
-- do NOT do something like this local ret = ""; for i=1, C do ret = ret..foo(); end
如果foo()
将只返回字符A
,这个循环将产生一系列像琴弦""
,"A"
,"AA"
,"AAA"
,等.每个字符串将被散列并驻留在内存中,直到应用程序结束-在这里看到了问题?
-- this is a lot faster local ret = {}; for i=1, C do ret[#ret+1] = foo(); end ret = table.concat(ret);
此方法在循环期间根本不创建字符串,字符串在函数中创建foo
,只有引用被复制到表中.然后,concat创建第二个字符串"AAAAAA..."
(取决于有多大C
).请注意,您可以使用i
而不是#ret+1
通常没有这样一个有用的循环,并且您不会使用可以使用的迭代器变量.
我在lua-users.org上找到的另一个技巧是使用gsub,如果你必须解析一个字符串
some_string:gsub(".", function(m) return "A"; end);
这看起来很奇怪,好处是gsub在C中"一次"创建一个字符串,只有在gsub返回后传递回lua时才会进行哈希处理.这样可以避免创建表,但可能会有更多的函数开销(foo()
不管你是否调用,但是如果foo()
它实际上是一个表达式)
尽可能使用语言结构而不是函数
ipairs
迭代表时,ipairs的函数开销并不能证明它的使用.要迭代表,而是使用
for k=1, #tbl do local v = tbl[k];
它在没有函数调用开销的情况下完全相同(对实际上返回另一个函数,然后为表中的每个元素调用,而#tbl
只评估一次).即使您需要价值,它也要快得多.如果你不......
注意Lua的5.2:5.2实际上你可以定义__ipairs
在元表,该场没有使ipairs
在某些情况下是有用的.但是,Lua 5.2也使该__len
字段适用于表,因此您可能仍然更喜欢上面的代码,ipairs
因为__len
元方法只调用一次,而对于ipairs
每次迭代,您将获得一个额外的函数调用.
table.insert
,table.remove
简单的使用table.insert
和table.remove
可以使用#
运算符代替.基本上这是简单的推送和弹出操作.这里有些例子:
table.insert(foo, bar); -- does the same as foo[#foo+1] = bar; local x = table.remove(foo); -- does the same as local x = foo[#foo]; foo[#foo] = nil;
对于移位(例如table.remove(foo, 1)
),如果不希望以稀疏表结束,那么使用表函数当然更好.
您可能 - 或可能不 - 在您的代码中做出如下决定
if a == "C" or a == "D" or a == "E" or a == "F" then ... end
现在这是一个非常有效的案例,但是(根据我自己的测试)从4个比较开始并排除表生成,这实际上更快:
local compares = { C = true, D = true, E = true, F = true }; if compares[a] then ... end
并且由于哈希表具有恒定的查找时间,因此每增加一次比较,性能增益就会增加.另一方面,如果"大部分时间"一两个比较匹配,那么使用布尔方式或组合可能会更好.
避免频繁创建表这在Lua Performance Tips中进行了详细讨论.基本上问题是Lua按需分配你的表,这样做实际上需要花费更多的时间来清理它的内容并再次填充它.
但是,这是一个问题,因为Lua本身不提供从表中删除所有元素的方法,而pairs()
不是性能野兽本身.我自己还没有对这个问题做过任何性能测试.
如果可以,定义一个清除表的C函数,这应该是表重用的一个很好的解决方案.
避免一遍又一遍地做同样的事情我认为这是最大的问题.虽然使用非解释语言的编译器可以轻松地优化大量冗余,但Lua不会.
使用表格可以在Lua中轻松完成.对于单参数函数,您甚至可以用表和__index元方法替换它们.即使这会破坏透明度,但由于少了一个函数调用,因此在缓存值上的性能更好.
这是使用metatable对单个参数进行memoization的实现.(重要提示:该变种不不支持零值的说法,但对于现有的值非常该死的快.)
function tmemoize(func) return setmetatable({}, { __index = function(self, k) local v = func(k); self[k] = v return v; end }); end -- usage (does not support nil values!) local mf = tmemoize(myfunc); local v = mf[x];
您实际上可以为多个输入值修改此模式
这个想法类似于memoization,即"缓存"结果.但是这里不是缓存函数的结果,而是通过将计算放在构造函数中来缓存中间值,该函数定义了它的块中的计算函数.实际上我只是称它巧妙地使用了闭包.
-- Normal function function foo(a, b, x) return cheaper_expression(expensive_expression(a,b), x); end -- foo(a,b,x1); -- foo(a,b,x2); -- ... -- Partial application function foo(a, b) local C = expensive_expression(a,b); return function(x) return cheaper_expression(C, x); end end -- local f = foo(a,b); -- f(x1); -- f(x2); -- ...
通过这种方式,可以轻松创建灵活的功能,缓存部分工作,而不会对程序流产生太大影响.
这种情况的一个极端变体是Currying,但实际上这更像是一种模仿函数式编程的方法.
这是一个更广泛的("现实世界")示例,其中包含一些代码遗漏,否则它将很容易占用整个页面(即get_color_values
实际上进行了大量的值检查并识别接受混合值)
function LinearColorBlender(col_from, col_to) local cfr, cfg, cfb, cfa = get_color_values(col_from); local ctr, ctg, ctb, cta = get_color_values(col_to); local cdr, cdg, cdb, cda = ctr-cfr, ctg-cfg, ctb-cfb, cta-cfa; if not cfr or not ctr then error("One of given arguments is not a color."); end return function(pos) if type(pos) ~= "number" then error("arg1 (pos) must be in range 0..1"); end if pos < 0 then pos = 0; end; if pos > 1 then pos = 1; end; return cfr + cdr*pos, cfg + cdg*pos, cfb + cdb*pos, cfa + cda*pos; end end -- Call local blender = LinearColorBlender({1,1,1,1},{0,0,0,1}); object:SetColor(blender(0.1)); object:SetColor(blender(0.3)); object:SetColor(blender(0.7));
您可以看到,一旦创建了搅拌机,该功能只需要对一个值进行完整性检查,而不是最多八次.我甚至提取了差异计算,虽然它可能没有很大改进,但我希望它能说明这种模式试图实现的目标.
如果您的lua程序真的太慢,请使用Lua分析器并清理昂贵的东西或迁移到C.但如果您没有坐在那里等待,那么您的时间浪费了.
第一个优化定律:不要.
我很想看到一个问题,你可以在ipairs和pair之间做出选择,并可以衡量差异的影响.
一个简单易懂的结果是记住在每个模块中使用局部变量.一般不值得做像这样的事情
local strfind = string.find
除非你能找到一个告诉你的测量.