当前位置 : 主页 > 网络编程 > lua >

我可以做些什么来提高Lua程序的性能?

来源:互联网 收集:自由互联 发布时间:2021-06-23
我问了一个关于Lua自动的问题,而在 responses问: Have you studied general tips for keeping Lua performance high? i.e. know table creation and rather reuse a table than create a new one, use of ‘local print=print’ and suc
我问了一个关于Lua自动的问题,而在 responses问:

Have you studied general tips for keeping Lua performance high? i.e. know table creation and rather reuse a table than create a new one, use of ‘local print=print’ and such to avoid global accesses.

这是一个与Lua Patterns,Tips and Tricks稍微不同的问题,因为我想要具体影响性能的答案(如果可能的话)解释为什么性能受到影响。

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

针对其他一些答案和评论:

确实,作为程序员,你应该通常避免过早的优化。但。对于编译器没有优化的脚本语言,这不是真的。

所以,每当你在Lua写的东西,经常被执行的时候,都是在一个时间紧迫的环境中运行,或者运行一段时间,知道要避免的事情是一件好事(避免它们)。

这是我随着时间的推移而发现的一个集合。其中一些我发现在网上,但是当互联网关注我自己测试了所有这一切,这是一个可疑的性质。另外,我已经阅读了Luaorg的Lua演示文稿。

一些参考:

> Lua Performance Tips
> Lua-users.org Optimisation Tips

避免全局变量

这是最常见的提示之一,但再次表示不能伤害。

全局变量以其名称存储在哈希表中。访问它们意味着您必须访问表索引。虽然Lua具有非常好的哈希表实现,但它比访问局部变量还要慢很多。如果您必须使用全局变量,请将其值分配给局部变量,这在第2个变量访问时更快。

do
  x = gFoo + gFoo;
end
do -- this actually performs better.
  local lFoo = gFoo;
  x = lFoo + lFoo;
end

(不是那么简单的测试可能会产生不同的结果,例如:local x;对于i = 1,1000 do x = i;在这里结束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中创建一个字符串“C”,只有当gsub返回后,它才被传递回lua。这避免了表创建,但可能有更多的功能开销(不是如果你调用foo(),但是如果foo()实际上是一个表达式)

避免功能开销

尽可能使用语言结构而不是功能

功能ipairs

当迭代表时,ipairs的函数开销不能证明它的用法。迭代一个表,而不是使用

for k=1, #tbl do local v = tbl[k];

它没有函数调用开销完全相同(对实际上返回另一个函数,然后对表中的每个元素进行调用,而#tbl仅被计算一次)。即使你需要这个价值,还要快很多。如果你不…

Lua 5.2的注意事项:在5.2中,您实际上可以在metatable中定义一个__ipairs字段,这在某些情况下会使ipairs变得有用。然而,Lua 5.2也使__len字段用于表,所以您可能仍然喜欢上述代码ipairs,因为__len metamethod仅被调用一次,而对于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)),如果以稀疏表结尾是不可取的,那么使用表函数当然更好。

使用SQL-IN表来比较

您可能 – 或可能不会在您的代码中做出以下决定

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本身不提供从表中删除所有元素的方法,而pair()不是本身的性能。我还没有对这个问题进行任何性能测试。

如果可以,定义清除表的C函数,这应该是表重用的一个很好的解决方案。

避免一再做同样的事情

这是最大的问题,我想。虽然非解释语言的编译器可以很容易地优化掉很多冗余,但Lua不会。

memoize的

使用表,这可以很容易地在Lua中完成。对于单参数函数,您甚至可以使用表和__index metamethod替换它们。即使这样破坏了透明度,由于一个较少的函数调用,缓存值的性能更好。

这是一个使用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];

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

Partial application

这个想法与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));

您可以看到,一旦创建了搅拌机,该功能只需要理智 – 检查单个值,而不是最多八个。我甚至提取差异计算,虽然它可能不会改善很多,但我希望它能显示出这种模式试图实现的。

网友评论