I asked a question about Lua perfromance, and on of the responses asked:
我问了一个关于Lua性能的问题,并询问了回答:
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性能高的一般技巧?即知道表创建而不是重用表而不是创建新表,使用'local print = print'等来避免全局访问。
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.
这是一个与Lua模式,技巧和诀窍略有不同的问题,因为我想要特别影响性能的答案,并且(如果可能的话)解释为什么性能会受到影响。
One tip per answer would be ideal.
每个答案一个提示是理想的。
5 个解决方案
#1
57
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.
确实,作为程序员,您通常应该避免过早优化。但。对于脚本语言而言,情况并非如此,因为编译器的优化程度不高 - 或者根本没有。
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).
因此,无论何时在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.org上阅读了Lua性能论文。
Some reference:
- Lua Performance Tips
- Lua-users.org Optimisation Tips
Lua性能提示
Lua-users.org优化技巧
Avoid globals
This is one of the most common hints, but stating it once more can't hurt.
这是最常见的提示之一,但再次说明它不会受到伤害。
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.
Globals按名称存储在哈希表中。访问它们意味着您必须访问表索引。虽然Lua有一个非常好的哈希表实现,但它仍然比访问局部变量慢很多。如果必须使用全局变量,将它们的值赋给局部变量,则在第二次变量访问时速度更快。
do
x = gFoo + gFoo;
end
do -- this actually performs better.
local lFoo = gFoo;
x = lFoo + lFoo;
end
(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.)
(不是那么简单的测试可能会产生不同的结果。例如,本地x;对于i = 1,1000做x = i;这里结束for循环标题实际上比循环体更多的时间,因此分析结果可能会失真。)
Avoid string creation
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.
Lua在创建时散列所有字符串,这使得比较和在表中非常快速地使用它们并减少内存使用,因为所有字符串仅在内部存储一次。但它使字符串创建更加昂贵。
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
避免过多字符串创建的流行选项是使用表。例如,如果你必须组装一个长字符串,创建一个表,将各个字符串放在那里,然后使用table.concat将它连接一次
-- do NOT do something like this
local ret = "";
for i=1, C do
ret = ret..foo();
end
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?
如果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);
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.
在循环期间,此方法根本不创建字符串,字符串在函数foo中创建,只有引用被复制到表中。然后,concat创建第二个字符串“AAAAAA ...”(取决于C的大小)。请注意,您可以使用i而不是#ret + 1,但通常您没有这样一个有用的循环,并且您将不会使用可以使用的迭代器变量。
Another trick I found somewhere on lua-users.org is to use gsub if you have to parse a string
我在lua-users.org上找到的另一个技巧是使用gsub,如果你必须解析一个字符串
some_string:gsub(".", function(m)
return "A";
end);
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)
这看起来很奇怪,好处是gsub在C中“立刻”创建了一个字符串,只有在gsub返回后传递回lua时才会进行哈希处理。这样可以避免创建表,但可能会有更多的函数开销(不管你是否调用foo(),但是如果foo()实际上是一个表达式)
Avoid function overhead
Use language constructs instead of functions where possible
尽可能使用语言结构而不是函数
function ipairs
When iterating a table, the function overhead from ipairs does not justify it's use. To iterate a table, instead use
在迭代表时,ipairs的函数开销并不能证明它的用法。要迭代表,而是使用
for k=1, #tbl do local v = tbl[k];
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...
它在没有函数调用开销的情况下完全相同(对实际上返回另一个函数,然后为表中的每个元素调用,而#tbl仅被评估一次)。即使您需要价值,它也会快得多。如果你不......
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.
注意Lua 5.2:在5.2中你可以在metatable中实际定义一个__ipairs字段,它确实使ipairs在某些情况下有用。但是,Lua 5.2也使__len字段适用于表,所以你可能仍然更喜欢上面的代码到ipairs,因为__len元方法只调用一次,而对于ipairs,你会在每次迭代时得到一个额外的函数调用。
functions table.insert
, table.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和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;
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.
对于移位(例如table.remove(foo,1)),如果不希望以稀疏表结束,那么使用表函数当然更好。
Use tables for SQL-IN alike compares
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
Now this is a perfectly valid case, however (from my own testing) starting with 4 comparisons and excluding table generation, this is actually faster:
现在这是一个非常有效的案例,但是(根据我自己的测试)从4次比较开始并排除表生成,这实际上更快:
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.
由于哈希表具有恒定的查找时间,因此每增加一次比较,性能增益就会增加。另一方面,如果“大部分时间”一两个比较匹配,那么使用布尔方式或组合可能会更好。
Avoid frequent table creation
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 Performance Tips中进行了详细讨论。基本上问题是Lua按需分配你的表,这样做实际上需要花费更多的时间来清理它的内容并再次填充它。
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.
但是,这有点问题,因为Lua本身不提供从表中删除所有元素的方法,而pair()不是性能野兽本身。我自己还没有对这个问题做过任何性能测试。
If you can, define a C function that clears a table, this should be a good solution for table reuse.
如果可以,定义一个清除表的C函数,这应该是表重用的一个很好的解决方案。
Avoid doing the same over and over
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不会。
Memoize
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.
使用表格可以在Lua中轻松完成。对于单参数函数,您甚至可以用表和__index元方法替换它们。即使这会破坏透明度,但由于函数调用较少,性能在缓存值上更好。
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.)
这是使用metatable对单个参数进行memoization的实现。 (重要:此变体不支持nil值参数,但对于现有值非常快。)
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
您实际上可以为多个输入值修改此模式
Partial application
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.
这个想法类似于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);
-- ...
This way it is possible to easily create flexible functions that cache some of their work without too much impact on program flow.
通过这种方式,可以轻松创建灵活的功能,缓存部分工作,而不会对程序流产生太大影响。
An extreme variant of this would be Currying, but that is actually more a way to mimic functional programming than anything else.
这种情况的一个极端变体是Currying,但这实际上更像是一种模仿函数式编程的方法。
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)
这是一个更广泛的(“现实世界”)示例,其中包含一些代码遗漏,否则它将很容易占用整个页面(即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));
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.
您可以看到,一旦创建了搅拌机,该功能只需要检查单个值而不是最多8个值。我甚至提取了差异计算,虽然它可能没有很大改进,但我希望它能说明这种模式试图实现的目标。
#2
8
If your lua program is really too slow, use the Lua profiler and clean up expensive stuff or migrate to C. But if you're not sitting there waiting, your time is wasted.
如果您的lua程序真的太慢,请使用Lua分析器并清理昂贵的东西或迁移到C.但如果您不是坐在那里等待,那么您的时间就浪费了。
The first law of optimization: Don't.
第一个优化定律:不要。
I'd love to see a problem where you have a choice between ipairs and pairs and can measure the effect of the difference.
我希望看到一个问题,你可以在ipairs和pair之间做出选择,并可以衡量差异的影响。
The one easy piece of low-hanging fruit is to remember to use local variables within each module. It's general not worth doing stuff like
简单易懂的一个方面是记住在每个模块中使用局部变量。一般不值得这样做
local strfind = string.find
unless you can find a measurement telling you otherwise.
除非您能找到告诉您的测量结果。
#3
3
- Making the most used functions locals
- Making good use of tables as HashSets
- Lowering table creation by reutilization
- Using luajit!
使用最常用的功能本地
充分利用表作为HashSets
通过重用来降低表创建
#4
1
Keep tables short, the larger the table the longer the search time. And in the same line iterating over numerically indexed tables (=arrays) is faster than key based tables (thus ipairs is faster than pairs)
保持表格较短,表格越大,搜索时间越长。并且在同一行中迭代数字索引表(=数组)比基于键的表更快(因此ipairs比对更快)
#5
1
It must be also pointed that using array fields from tables is much faster than using tables with any kind of key. It happens (almost) all Lua implementations (including LuaJ) store a called "array part" inside tables, which is accessed by the table array fields, and doesn't store the field key, nor lookup for it ;).
还必须指出,使用表中的数组字段比使用任何类型的键的表要快得多。它发生(几乎)所有Lua实现(包括LuaJ)在表中存储一个被称为“数组部分”的表,它由表数组字段访问,并且不存储字段键,也不查找它;)。
You can even also imitate static aspects of other languages like struct
, C++/Java class
, etc.. Locals and arrays are enough.
您甚至可以模仿其他语言的静态方面,如struct,C ++ / Java class等。本地和数组就足够了。
#1
57
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.
确实,作为程序员,您通常应该避免过早优化。但。对于脚本语言而言,情况并非如此,因为编译器的优化程度不高 - 或者根本没有。
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).
因此,无论何时在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.org上阅读了Lua性能论文。
Some reference:
- Lua Performance Tips
- Lua-users.org Optimisation Tips
Lua性能提示
Lua-users.org优化技巧
Avoid globals
This is one of the most common hints, but stating it once more can't hurt.
这是最常见的提示之一,但再次说明它不会受到伤害。
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.
Globals按名称存储在哈希表中。访问它们意味着您必须访问表索引。虽然Lua有一个非常好的哈希表实现,但它仍然比访问局部变量慢很多。如果必须使用全局变量,将它们的值赋给局部变量,则在第二次变量访问时速度更快。
do
x = gFoo + gFoo;
end
do -- this actually performs better.
local lFoo = gFoo;
x = lFoo + lFoo;
end
(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.)
(不是那么简单的测试可能会产生不同的结果。例如,本地x;对于i = 1,1000做x = i;这里结束for循环标题实际上比循环体更多的时间,因此分析结果可能会失真。)
Avoid string creation
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.
Lua在创建时散列所有字符串,这使得比较和在表中非常快速地使用它们并减少内存使用,因为所有字符串仅在内部存储一次。但它使字符串创建更加昂贵。
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
避免过多字符串创建的流行选项是使用表。例如,如果你必须组装一个长字符串,创建一个表,将各个字符串放在那里,然后使用table.concat将它连接一次
-- do NOT do something like this
local ret = "";
for i=1, C do
ret = ret..foo();
end
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?
如果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);
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.
在循环期间,此方法根本不创建字符串,字符串在函数foo中创建,只有引用被复制到表中。然后,concat创建第二个字符串“AAAAAA ...”(取决于C的大小)。请注意,您可以使用i而不是#ret + 1,但通常您没有这样一个有用的循环,并且您将不会使用可以使用的迭代器变量。
Another trick I found somewhere on lua-users.org is to use gsub if you have to parse a string
我在lua-users.org上找到的另一个技巧是使用gsub,如果你必须解析一个字符串
some_string:gsub(".", function(m)
return "A";
end);
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)
这看起来很奇怪,好处是gsub在C中“立刻”创建了一个字符串,只有在gsub返回后传递回lua时才会进行哈希处理。这样可以避免创建表,但可能会有更多的函数开销(不管你是否调用foo(),但是如果foo()实际上是一个表达式)
Avoid function overhead
Use language constructs instead of functions where possible
尽可能使用语言结构而不是函数
function ipairs
When iterating a table, the function overhead from ipairs does not justify it's use. To iterate a table, instead use
在迭代表时,ipairs的函数开销并不能证明它的用法。要迭代表,而是使用
for k=1, #tbl do local v = tbl[k];
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...
它在没有函数调用开销的情况下完全相同(对实际上返回另一个函数,然后为表中的每个元素调用,而#tbl仅被评估一次)。即使您需要价值,它也会快得多。如果你不......
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.
注意Lua 5.2:在5.2中你可以在metatable中实际定义一个__ipairs字段,它确实使ipairs在某些情况下有用。但是,Lua 5.2也使__len字段适用于表,所以你可能仍然更喜欢上面的代码到ipairs,因为__len元方法只调用一次,而对于ipairs,你会在每次迭代时得到一个额外的函数调用。
functions table.insert
, table.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和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;
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.
对于移位(例如table.remove(foo,1)),如果不希望以稀疏表结束,那么使用表函数当然更好。
Use tables for SQL-IN alike compares
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
Now this is a perfectly valid case, however (from my own testing) starting with 4 comparisons and excluding table generation, this is actually faster:
现在这是一个非常有效的案例,但是(根据我自己的测试)从4次比较开始并排除表生成,这实际上更快:
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.
由于哈希表具有恒定的查找时间,因此每增加一次比较,性能增益就会增加。另一方面,如果“大部分时间”一两个比较匹配,那么使用布尔方式或组合可能会更好。
Avoid frequent table creation
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 Performance Tips中进行了详细讨论。基本上问题是Lua按需分配你的表,这样做实际上需要花费更多的时间来清理它的内容并再次填充它。
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.
但是,这有点问题,因为Lua本身不提供从表中删除所有元素的方法,而pair()不是性能野兽本身。我自己还没有对这个问题做过任何性能测试。
If you can, define a C function that clears a table, this should be a good solution for table reuse.
如果可以,定义一个清除表的C函数,这应该是表重用的一个很好的解决方案。
Avoid doing the same over and over
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不会。
Memoize
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.
使用表格可以在Lua中轻松完成。对于单参数函数,您甚至可以用表和__index元方法替换它们。即使这会破坏透明度,但由于函数调用较少,性能在缓存值上更好。
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.)
这是使用metatable对单个参数进行memoization的实现。 (重要:此变体不支持nil值参数,但对于现有值非常快。)
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
您实际上可以为多个输入值修改此模式
Partial application
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.
这个想法类似于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);
-- ...
This way it is possible to easily create flexible functions that cache some of their work without too much impact on program flow.
通过这种方式,可以轻松创建灵活的功能,缓存部分工作,而不会对程序流产生太大影响。
An extreme variant of this would be Currying, but that is actually more a way to mimic functional programming than anything else.
这种情况的一个极端变体是Currying,但这实际上更像是一种模仿函数式编程的方法。
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)
这是一个更广泛的(“现实世界”)示例,其中包含一些代码遗漏,否则它将很容易占用整个页面(即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));
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.
您可以看到,一旦创建了搅拌机,该功能只需要检查单个值而不是最多8个值。我甚至提取了差异计算,虽然它可能没有很大改进,但我希望它能说明这种模式试图实现的目标。
#2
8
If your lua program is really too slow, use the Lua profiler and clean up expensive stuff or migrate to C. But if you're not sitting there waiting, your time is wasted.
如果您的lua程序真的太慢,请使用Lua分析器并清理昂贵的东西或迁移到C.但如果您不是坐在那里等待,那么您的时间就浪费了。
The first law of optimization: Don't.
第一个优化定律:不要。
I'd love to see a problem where you have a choice between ipairs and pairs and can measure the effect of the difference.
我希望看到一个问题,你可以在ipairs和pair之间做出选择,并可以衡量差异的影响。
The one easy piece of low-hanging fruit is to remember to use local variables within each module. It's general not worth doing stuff like
简单易懂的一个方面是记住在每个模块中使用局部变量。一般不值得这样做
local strfind = string.find
unless you can find a measurement telling you otherwise.
除非您能找到告诉您的测量结果。
#3
3
- Making the most used functions locals
- Making good use of tables as HashSets
- Lowering table creation by reutilization
- Using luajit!
使用最常用的功能本地
充分利用表作为HashSets
通过重用来降低表创建
#4
1
Keep tables short, the larger the table the longer the search time. And in the same line iterating over numerically indexed tables (=arrays) is faster than key based tables (thus ipairs is faster than pairs)
保持表格较短,表格越大,搜索时间越长。并且在同一行中迭代数字索引表(=数组)比基于键的表更快(因此ipairs比对更快)
#5
1
It must be also pointed that using array fields from tables is much faster than using tables with any kind of key. It happens (almost) all Lua implementations (including LuaJ) store a called "array part" inside tables, which is accessed by the table array fields, and doesn't store the field key, nor lookup for it ;).
还必须指出,使用表中的数组字段比使用任何类型的键的表要快得多。它发生(几乎)所有Lua实现(包括LuaJ)在表中存储一个被称为“数组部分”的表,它由表数组字段访问,并且不存储字段键,也不查找它;)。
You can even also imitate static aspects of other languages like struct
, C++/Java class
, etc.. Locals and arrays are enough.
您甚至可以模仿其他语言的静态方面,如struct,C ++ / Java class等。本地和数组就足够了。