我该怎么做才能提高Lua程序的性能?

时间:2008-09-30 19:51:12

标签: performance optimization lua

我问了一个关于Lua性能的问题,以及responses问的问题:

  

您是否研究过保持Lua性能高的一般提示?即知道表创建而不是重用表而不是创建新表,使用'local print = print'等来避免全局访问。

这是与Lua Patterns,Tips and Tricks略有不同的问题,因为我希望答案能够明显影响效果,并且(如果可能的话)解释为什么会影响效果。

每个答案一个提示是理想的。

5 个答案:

答案 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()实际上是表达式)

避免功能开销

在可能的情况下使用语言结构而不是函数

function 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,每次迭代都会得到一个额外的函数调用。

函数ipairstable.insert

可以使用table.remove运算符替换table.inserttable.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; ),如果不希望以稀疏表结束,那么使用表格函数当然更好。

使用表格进行SQL-IN比较

您可能 - 或者可能不会 - 在您的代码中做出决定,如下所示

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不会。

memoize的

使用表格可以在Lua中轻松完成。对于单参数函数,您甚至可以用表和__index元方法替换它们。即使这会破坏透明度,但由于函数调用较少,性能在缓存值上会更好。

这是使用metatable对单个参数进行memoization的实现。 (重要:此变体支持nil值参数,但对现有值来说非常快。)

pairs()

您实际上可以为多个输入值修改此模式

Partial application

这个想法类似于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)

  • 制作最常用的本地函数
  • 充分利用表格作为HashSets
  • 通过重用度降低表创建
  • 使用luajit!

答案 3 :(得分:2)

保持表格较短,表格越大,搜索时间越长。 并且在同一行中迭代数字索引表(=数组)比基于键的表更快(因此ipairs比对更快)

答案 4 :(得分:2)

还必须指出,使用表中的数组字段比使用任何类型的键的表要快得多。它发生(几乎)所有Lua实现(包括LuaJ)存储一个被调用的"数组部分"内部表,由表数组字段访问,并且不存储字段键,也不查找它;)。

您甚至可以模仿其他语言的静态方面,如struct,C ++ / Java class等。本地和数组就足够了。