我问了一个关于Lua性能的问题,以及responses问的问题:
您是否研究过保持Lua性能高的一般提示?即知道表创建而不是重用表而不是创建新表,使用'local print = print'等来避免全局访问。
这是与Lua Patterns,Tips and Tricks略有不同的问题,因为我希望答案能够明显影响效果,并且(如果可能的话)解释为什么会影响效果。
每个答案一个提示是理想的。
答案 0 :(得分:58)
回应其他一些答案和评论:
确实,作为程序员,您通常应该避免过早优化。 但是。对于脚本语言而言,情况并非如此,因为编译器的优化程度不高 - 或者根本没有。
所以,无论何时你在Lua中写一些东西,并且经常执行,在一个时间关键的环境中运行,或者可以运行一段时间,知道避免(并避免它们)。
这是我发现的一段时间的集合。其中一些是我在网上发现的,但是当 the interwebs 关注时,我有一种可疑的性质,我自己测试了所有这些。另外,我在Lua.org上阅读了Lua性能论文。
一些参考:
这是最常见的提示之一,但再次说明它可能会受到伤害。
全局变量按名称存储在哈希表中。访问它们意味着您必须访问表索引。虽然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"
,{{1每个字符串都会被散列并驻留在内存中,直到应用程序完成 - 请看这里的问题?
"AAA"
此方法在循环期间根本不创建字符串,字符串在函数-- this is a lot faster
local ret = {};
for i=1, C do
ret[#ret+1] = foo();
end
ret = table.concat(ret);
中创建,只有引用被复制到表中。然后,concat创建第二个字符串foo
(取决于"AAAAAA..."
的大小)。请注意,您可以使用C
代替i
,但通常您没有这样一个有用的循环,并且您不会拥有迭代器变量使用
我在lua-users.org上找到的另一个技巧是使用gsub,如果你必须解析一个字符串
#ret+1
一开始看起来很奇怪,好处是gsub会立刻创建一个字符串""在C中,当gsub返回时它被传递回lua后才进行哈希处理。这样可以避免创建表,但可能会有更多的函数开销(无论如何你调用some_string:gsub(".", function(m)
return "A";
end);
,但如果foo()
实际上是表达式)
在可能的情况下使用语言结构而不是函数
foo()
当迭代表时,来自ipairs的函数开销并不能证明它的合理性。要迭代表,请使用
ipairs
在没有函数调用开销的情况下它完全相同(对实际上返回另一个函数,然后为表中的每个元素调用,而for k=1, #tbl do local v = tbl[k];
仅评估一次)。即使您需要价值,它也会快得多。如果你不......
Lua 5.2注意事项:在5.2中,您实际上可以在元表中定义#tbl
字段,其中 使__ipairs
对某些字段有用案例。但是,Lua 5.2还使ipairs
字段适用于表,因此您仍然更喜欢上述代码__len
,因为ipairs
metamethod只被调用一次,对于__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)
现在这是一个非常有效的案例,但是(根据我自己的测试)从4次比较开始,不包括表格生成,这实际上更快:
if a == "C" or a == "D" or a == "E" or a == "F" then
...
end
由于哈希表具有恒定的查找时间,因此每增加一次比较,性能增益就会增加。另一方面,如果"大部分时间"一两个比较匹配,使用布尔方式或组合可能会更好。
在Lua Performance Tips中对此进行了详细讨论。基本上问题是Lua按需分配你的表,这样做实际上需要花费更多的时间来清理它的内容并再次填充它。
然而,这是一个问题,因为Lua本身不提供从表中删除所有元素的方法,而local compares = { C = true, D = true, E = true, F = true };
if compares[a] then
...
end
不是性能野兽本身。我自己还没有对这个问题进行任何性能测试。
如果可以,定义一个清除表的C函数,这应该是表重用的一个很好的解决方案。
我认为这是最大的问题。虽然使用非解释性语言的编译器可以轻松地优化大量冗余,但Lua不会。
使用表格可以在Lua中轻松完成。对于单参数函数,您甚至可以用表和__index元方法替换它们。即使这会破坏透明度,但由于函数调用较少,性能在缓存值上会更好。
这是使用metatable对单个参数进行memoization的实现。 (重要:此变体不支持nil值参数,但对现有值来说非常快。)
pairs()
您实际上可以为多个输入值修改此模式
这个想法类似于memoization,即" cache"结果。但是这里不是缓存函数的结果,而是通过将计算放在构造函数中来缓存中间值,该函数定义了它的块中的计算函数。实际上,我只是称它巧妙地使用了闭包。
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];
通过这种方式,可以轻松创建灵活的功能,缓存部分工作,而不会对程序流产生太大影响。
这个的一个极端变体是Currying,但这实际上更像是一种模仿函数式编程的方法。
这是一个更广泛的("真实世界")示例,其中包含一些代码遗漏,否则它会轻易占用整个页面(即-- 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);
-- ...
实际上会进行大量的值检查识别接受混合值)
get_color_values
您可以看到,一旦创建了搅拌机,该功能只需要检查单个值而不是最多8个值。我甚至提取了差异计算,虽然它可能没有很大改进,但我希望它能说明这种模式试图实现的目标。
答案 1 :(得分:9)
如果您的lua程序真的太慢,请使用Lua分析器并清理昂贵的东西或迁移到C.但如果您没有坐在那里等待,那么您的时间就会浪费。
优化的第一定律:不要。
我很想看到一个问题,你可以在ipairs和pair之间做出选择,并可以衡量差异的影响。
一个简单易懂的方法是记住在每个模块中使用局部变量。一般不值得做像
这样的事情local strfind = string.find
除非你能找到一个告诉你的测量结果。
答案 2 :(得分:4)
答案 3 :(得分:2)
保持表格较短,表格越大,搜索时间越长。 并且在同一行中迭代数字索引表(=数组)比基于键的表更快(因此ipairs比对更快)
答案 4 :(得分:2)
还必须指出,使用表中的数组字段比使用任何类型的键的表要快得多。它发生(几乎)所有Lua实现(包括LuaJ)存储一个被调用的"数组部分"内部表,由表数组字段访问,并且不存储字段键,也不查找它;)。
您甚至可以模仿其他语言的静态方面,如struct
,C ++ / Java class
等。本地和数组就足够了。