如何提高Lua程序的性能? [英] What can I do to increase the performance of a Lua program?

查看:104
本文介绍了如何提高Lua程序的性能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I asked a question about Lua perfromance, and on of the responses asked:

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

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模式,技巧和窍门稍有不同的问题,因为我会例如专门影响绩效的答案,以及(如果可能)解释为何会影响绩效的原因.

This is a slightly different question from Lua Patterns,Tips and Tricks because I'd like answers that specifically impact performance and (if possible) an explanation of why performance is impacted.

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

One tip per answer would be ideal.

推荐答案

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

In response to some of the other answers and comments:

确实,作为程序员,您通常应该避免过早的优化. 但是.对于脚本语言,编译器并没有太多优化,甚至根本没有优化.

It is true that as a programmer you should generally avoid premature optimization. But. This is not so true for scripting languages where the compiler does not optimize much -- or at all.

因此,每当您在Lua中编写某些东西并且经常执行,在时间紧迫的环境中运行或者可能运行一段时间时,了解避免出现的事情都是一件好事. >(并避免出现).

So, whenever you write something in Lua, and that is executed very often, is run in a time-critical environment or could run for a while, it is a good thing to know things to avoid (and avoid them).

这是我随着时间推移发现的内容的集合.我通过网络发现了其中的一些内容,但是当我对 interwebs 表示怀疑时,我本人对其进行了全部测试.另外,我已经在Lua.org上阅读了Lua的性能报告.

This is a collection of what I found out over time. Some of it I found out over the net, but being of a suspicious nature when the interwebs are concerned I tested all of it myself. Also, I have read the Lua performance paper at Lua.org.

一些参考:

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

这是最常见的提示之一,但再说一次也不会造成伤害.

This is one of the most common hints, but stating it once more can't hurt.

全局变量按其名称存储在哈希表中.访问它们意味着您必须访问表索引.虽然Lua有一个很好的哈希表实现,但它仍然比访问局部变量慢很多.如果必须使用全局变量,请将其值分配给局部变量,这在第二次访问变量时会更快.

Globals are stored in a hashtable by their name. Accessing them means you have to access a table index. While Lua has a pretty good hashtable implementation, it's still a lot slower than accessing a local variable. If you have to use globals, assign their value to a local variable, this is faster at the 2nd variable access.

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循环头实际上比循环主体花费更多的时间,因此分析结果可能会失真.)

(Not that simple testing may yield different results. eg. local x; for i=1, 1000 do x=i; end here the for loop header takes actually more time than the loop body, thus profiling results could be distorted.)

Lua在创建时对所有字符串进行哈希处理,这使得比较和在表中使用它们非常快,并且减少了内存使用,因为所有字符串仅在内部存储一次.但这会使字符串创建更加昂贵.

Lua hashes all strings on creation, this makes comparison and using them in tables very fast and reduces memory use since all strings are stored internally only once. But it makes string creation more expensive.

一种避免表格过多创建字符串的流行选择是使用表.例如,如果您必须组装一个长字符串,请创建一个表,在其中放置各个字符串,然后使用table.concat将其连接一次

A popular option to avoid excessive string creation is using tables. For example, if you have to assemble a long string, create a table, put the individual strings in there and then use table.concat to join it once

-- do NOT do something like this
local ret = "";
for i=1, C do
  ret = ret..foo();
end

如果foo()仅返回字符A,则此循环将创建一系列字符串,例如"""A""AA""AAA"等.每个字符串都将被散列并驻留在内存中直到应用程序完成-在这里看到问题了吗?

If foo() would return only the character A, this loop would create a series of strings like "", "A", "AA", "AAA", etc. Each string would be hashed and reside in memory until the application finishes -- see the problem here?

-- 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,但是通常您没有这样有用的循环,并且您没有可以使用的迭代器变量.

This method does not create strings at all during the loop, the string is created in the function foo and only references are copied into the table. Afterwards, concat creates a second string "AAAAAA..." (depending on how large C is). Note that you could use i instead of #ret+1 but often you don't have such a useful loop and you won't have an iterator variable you can use.

在lua-users.org上我发现的另一个技巧是,如果必须解析字符串,则使用gsub

Another trick I found somewhere on lua-users.org is to use gsub if you have to parse a string

some_string:gsub(".", function(m)
  return "A";
end);

乍看之下很奇怪,好处是gsub在C中创建了一个字符串立即",仅当gsub返回时将其传递回lua后才对其进行哈希处理.这样可以避免创建表,但是可能会有更多的函数开销(无论如何都不能调用foo(),但是如果foo()实际上是一个表达式)

This looks odd at first, the benefit is that gsub creates a string "at once" in C which is only hashed after it is passed back to lua when gsub returns. This avoids table creation, but possibly has more function overhead (not if you call foo() anyway, but if foo() is actually an expression)

在可能的情况下使用语言构造代替函数

Use language constructs instead of functions where possible

迭代表时,ipairs产生的函数开销不足以证明其使用合理.要迭代表,请改用

When iterating a table, the function overhead from ipairs does not justify it's use. To iterate a table, instead use

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

它的功能完全一样,没有函数调用开销(实际上,对返回另一个函数,然后对表中的每个元素都调用该函数,而#tbl仅被评估一次).即使您需要此值,它也要快得多.如果你不...

It does exactly the same without the function call overhead (pairs actually returns another function which is then called for every element in the table while #tbl is only evaluated once). It's a lot faster, even if you need the value. And if you don't...

Lua 5.2的注意事项:在5.2中,您实际上可以在元表中定义__ipairs字段,确实使ipairs在某些情况下很有用.但是,Lua 5.2也会使__len字段适用于表,因此您可能 still 首选上述代码而不是ipairs,因为__len元方法仅被调用一次,而对于ipairs >您将在每次迭代中获得一个额外的函数调用.

Note for Lua 5.2: In 5.2 you can actually define a __ipairs field in the metatable, which does make ipairs useful in some cases. However, Lua 5.2 also makes the __len field work for tables, so you might still prefer the above code to ipairs as then the __len metamethod is only called once, while for ipairs you would get an additional function call per iteration.

table.inserttable.remove的简单用法可以代替使用#运算符.基本上,这是用于简单的推入和弹出操作.以下是一些示例:

Simple uses of table.insert and table.remove can be replaced by using the # operator instead. Basically this is for simple push and pop operations. Here are some examples:

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)),如果不希望以稀疏表结尾,那么使用表函数当然还是更好.

For shifts (eg. table.remove(foo, 1)), and if ending up with a sparse table is not desirable, it is of course still better to use the table functions.

您可能会(也可能不会)在您的代码中做出如下决定

You might - or might not - have decisions in your code like the following

if a == "C" or a == "D" or a == "E" or a == "F" then
   ...
end

现在这是一个非常有效的情况,但是(根据我自己的测试)从4个比较开始,但不包括表生成,这实际上更快:

Now this is a perfectly valid case, however (from my own testing) starting with 4 comparisons and excluding table generation, this is actually faster:

local compares = { C = true, D = true, E = true, F = true };
if compares[a] then
   ...
end

并且由于哈希表具有恒定的查找时间,因此每进行一次其他比较,性能就会提高.另一方面,如果大多数情况下"一次或两次比较匹配,则使用布尔值方式或组合方式可能会更好.

And since hash tables have constant look up time, the performance gain increases with every additional comparison. On the other hand if "most of the time" one or two comparisons match, you might be better off with the Boolean way or a combination.

Lua性能提示中对此进行了详尽的讨论.基本上,问题在于Lua按需分配表,用这种方法实际上要比清理表中的内容并再次填充它花费更多的时间.

This is discussed thoroughly in Lua Performance Tips. Basically the problem is that Lua allocates your table on demand and doing it this way will actually take more time than cleaning it's content and filling it again.

但是,这有点问题,因为Lua本身不提供从表中删除所有元素的方法,并且pairs()本身也不是性能野兽.我本人尚未对此性能进行任何性能测试.

However, this is a bit of a problem, since Lua itself does not provide a method for removing all elements from a table, and pairs() is not the performance beast itself. I have not done any performance testing on this problem myself yet.

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

If you can, define a C function that clears a table, this should be a good solution for table reuse.

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

This is the biggest problem, I think. While a compiler in a non-interpreted language can easily optimize away a lot of redundancies, Lua will not.

使用表格,可以在Lua中很容易地做到这一点.对于单参数函数,您甚至可以将它们替换为表和__index元方法.尽管这破坏了透明度,但由于减少了一次函数调用,因此在缓存值上的性能更好.

Using tables this can be done quite easily in Lua. For single-argument functions you can even replace them with a table and __index metamethod. Even though this destroys transparancy, performance is better on cached values due to one less function call.

这是使用元表对单个参数进行记忆的实现. (重要:此变体不支持nil值参数,但是对于现有值来说该死的非常快.)

Here is an implementation of memoization for a single argument using a metatable. (Important: This variant does not support a nil value argument, but is pretty damn fast for existing values.)

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];

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

You could actually modify this pattern for multiple input values

这个想法类似于记忆,即缓存"结果.但是在这里,不是缓存函数的结果,而是通过将中间值的计算放入构造函数中来缓存中间值,该构造函数在其块中定义了计算函数.实际上,我只是称其为闭包的巧妙用法.

The idea is similar to memoization, which is to "cache" results. But here instead of caching the results of the function, you would cache intermediate values by putting their calculation in a constructor function that defines the calculation function in it's block. In reality I would just call it clever use of closures.

-- 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);
-- ...

通过这种方式,可以轻松地创建灵活的函数来缓存其某些工作,而又不会对程序流造成太大影响.

This way it is possible to easily create flexible functions that cache some of their work without too much impact on program flow.

极端的变体是 Currying ,但这实际上是模仿函数式编程的一种方式

An extreme variant of this would be Currying, but that is actually more a way to mimic functional programming than anything else.

这是一个更广泛的(真实世界")示例,其中省略了一些代码,否则,此处很容易占用整个页面(即get_color_values实际上进行了大量的值检查并识别接受混合值)

Here is a more extensive ("real world") example with some code omissions, otherwise it would easily take up the whole page here (namely get_color_values actually does a lot of value checking and recognizes accepts mixed 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));

您可以看到,一旦创建了搅拌器,该函数只需对单个值进行健全性检查,而不是最多八个.我什至提取了差异计算,尽管它可能不会改善很多,但我希望它能显示出该模式试图实现的目标.

You can see that once the blender was created, the function only has to sanity-check a single value instead of up to eight. I even extracted the difference calculation, though it probably does not improve a lot, I hope it shows what this pattern tries to achieve.

这篇关于如何提高Lua程序的性能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆