当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《我该怎么做才能提高Lua程序的性能?》经验,为你挑选了2个好方法。

我问了一个关于Lua性能的问题,并询问了回答:

你有没有研究过保持Lua性能高的一般技巧?即知道表创建而不是重用表而不是创建新表,使用'local print = print'等来避免全局访问.

这是一个与Lua模式,提示和技巧略有不同的问题,因为我希望答案能够特别影响性能,并且(如果可能的话)解释为什么性能会受到影响.

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



1> dualed..:

回应其他一些答案和评论:

确实,作为程序员,您通常应该避免过早优化.但是.对于脚本语言而言,情况并非如此,因为编译器的优化程度不高 - 或根本没有.

因此,无论何时在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.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;

对于移位(例如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本身不提供从表中删除所有元素的方法,而pairs()不是性能野兽本身.我自己还没有对这个问题做过任何性能测试.

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

避免一遍又一遍地做同样的事情

我认为这是最大的问题.虽然使用非解释语言的编译器可以轻松地优化大量冗余,但Lua不会.

memoize的

使用表格可以在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));

您可以看到,一旦创建了搅拌机,该功能只需要对一个值进行完整性检查,而不是最多八次.我甚至提取了差异计算,虽然它可能没有很大改进,但我希望它能说明这种模式试图实现的目标.



2> Norman Ramse..:

如果您的lua程序真的太慢,请使用Lua分析器并清理昂贵的东西或迁移到C.但如果您没有坐在那里等待,那么您的时间浪费了.

第一个优化定律:不要.

我很想看到一个问题,你可以在ipairs和pair之间做出选择,并可以衡量差异的影响.

一个简单易懂的结果是记住在每个模块中使用局部变量.一般不值得做像这样的事情

local strfind = string.find

除非你能找到一个告诉你的测量.


是的,我知道,它被滥用来创建慢速软件到地狱和背部.也许它适用于Don自己,他总能断言需要优化的东西,什么不需要.对于90%的程序员来说,它的内容是"无论你的愚蠢算法有多慢,都不要优化它".这就是为什么我不喜欢它传播的原因.
推荐阅读
wangtao
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有