私はLuaのパフォーマンスについて質問し、 responses の質問:
Luaのパフォーマンスを高く保つための一般的なヒントを学びましたか?つまり、テーブルの作成を知っていて、新しいテーブルを作成するのではなくテーブルを再利用し、グローバルアクセスを回避するために「localprint = print」などを使用します。
これは Luaパターン、ヒント、コツ とは少し異なる質問です。パフォーマンスに特に影響を与える回答と、(可能であれば)パフォーマンスが影響を受ける理由の説明が必要だからです。
回答ごとに1つのヒントが理想的です。
他の回答やコメントのいくつかに応えて:
プログラマーとして、一般的に時期尚早の最適化を避けるべきであることは事実です。しかし。これは、コンパイラがあまり最適化しない、またはまったく最適化しないスクリプト言語には当てはまりません。
したがって、Luaで何かを記述し、それが非常に頻繁に実行される場合、タイムクリティカルな環境で実行される場合、またはしばらく実行される可能性がある場合は、avoidすることを知っておくとよいでしょう。(そしてそれらを避けてください)。
これは私が時間をかけて見つけたもののコレクションです。その一部はネットで見つけましたが、interwebsが懸念される場合は疑わしい性質があるため、すべて自分でテストしました。また、Lua.orgでLuaパフォーマンスペーパーを読みました。
いくつかの参照:
これは最も一般的なヒントの1つですが、もう一度述べることは害にはなりません。
グローバルは、名前でハッシュテーブルに格納されます。それらにアクセスするには、テーブルインデックスにアクセスする必要があります。 Luaにはかなり優れたハッシュテーブルの実装がありますが、それでもローカル変数にアクセスするよりもはるかに低速です。グローバルを使用する必要がある場合は、その値をローカル変数に割り当てます。これは、2番目の変数アクセスで高速になります。
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は作成時にすべての文字列をハッシュします。これにより、比較とテーブルでの使用が非常に高速になり、すべての文字列が内部に1回だけ保存されるため、メモリ使用量が削減されます。しかし、それは文字列の作成をより高価にします。
過度の文字列の作成を回避するための一般的なオプションは、テーブルを使用することです。たとえば、長い文字列を組み立てる必要がある場合は、テーブルを作成し、そこに個々の文字列を配置してから、table.concat
を使用して結合しますonce
-- 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は2番目の文字列"AAAAAA..."
を作成します(C
の大きさに応じて)。could#ret+1
の代わりにi
を使用しますが、多くの場合、そのような便利なループがなく、できるイテレータ変数がないことに注意してください。使用する。
Lua-users.orgのどこかで見つけたもう1つのトリックは、文字列を解析する必要がある場合に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
は1回だけ評価されます)。値が必要な場合でも、はるかに高速です。そして、あなたがしなければ...
Lua 5.2に関する注記:5.2では、メタテーブルに__ipairs
フィールドを実際に定義できます。これはdoes場合によってはipairs
を便利にします。ただし、Lua 5.2では__len
フィールドがテーブルでも機能するため、still上記のコードをipairs
よりも優先すると、__len
メタメソッドのみが呼び出されます。一度、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)
)の場合、およびスパーステーブルで終わることが望ましくない場合は、もちろん、テーブル関数を使用することをお勧めします。
コード内で次のような決定を下す場合もあれば、そうでない場合もあります。
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
また、ハッシュテーブルのルックアップ時間は一定であるため、比較を追加するたびにパフォーマンスが向上します。一方、「ほとんどの場合」1つまたは2つの比較が一致する場合は、ブール法またはその組み合わせを使用した方がよい場合があります。
これについては Lua Performance Tips で詳しく説明されています。基本的に問題は、Luaがテーブルをオンデマンドで割り当てることです。この方法で行うと、実際には、コンテンツをクリーンアップして再度入力するよりも時間がかかります。
ただし、Lua自体はテーブルからすべての要素を削除するメソッドを提供しておらず、pairs()
はパフォーマンスビースト自体ではないため、これは少し問題です。私はまだこの問題のパフォーマンステストを行っていません。
可能であれば、テーブルをクリアするC関数を定義します。これは、テーブルを再利用するための優れたソリューションになるはずです。
これが最大の問題だと思います。インタープリター型言語のコンパイラーは多くの冗長性を簡単に最適化できますが、Luaはそうしません。
テーブルを使用すると、Luaでこれを非常に簡単に行うことができます。単一引数関数の場合、それらをテーブルおよび__indexメタメソッドに置き換えることもできます。これにより透明性が失われますが、関数呼び出しが1つ少なくなるため、キャッシュされた値のパフォーマンスが向上します。
これは、メタテーブルを使用した単一の引数のメモ化の実装です。 (重要:このバリアントはnotnil値引数をサポートしますが、既存の値に対してはかなり高速です。)
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];
複数の入力値に対してこのパターンを実際に変更できます
この考え方は、結果を「キャッシュ」するメモ化に似ています。ただし、ここでは、関数の結果をキャッシュする代わりに、ブロック内の計算関数を定義するコンストラクター関数に計算を配置することにより、中間値をキャッシュします。実際には、私はそれをクロージャの巧妙な使用と呼んでいます。
-- 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
は実際に多くの値チェックを実行し、混合値を受け入れることを認識します)
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));
ブレンダーが作成されると、関数は最大8つではなく1つの値をサニティチェックするだけでよいことがわかります。差の計算も抽出しましたが、あまり改善されないかもしれませんが、このパターンが何を達成しようとしているのかを示していると思います。
Luaプログラムが本当に遅すぎる場合は、Luaプロファイラーを使用して高価なものをクリーンアップするか、Cに移行してください。しかし、そこに座って待っていない場合は、時間が無駄になります。
最適化の最初の法則:しないでください。
Ipairsとpairsのどちらかを選択でき、その違いの影響を測定できる問題を見つけたいと思います。
簡単な成果の1つは、各モジュール内でローカル変数を使用することを忘れないことです。一般的に、次のようなことをする価値はありません
local strfind = string.find
あなたがそうでないことを告げる測定値を見つけることができない限り。
また、テーブルの配列フィールドを使用する方が、任意の種類のキーを持つテーブルを使用するよりもはるかに高速であることも指摘しておく必要があります。 (ほぼ)すべてのLua実装(LuaJを含む)は、テーブル配列フィールドによってアクセスされるテーブル内に呼び出された「配列部分」を格納し、フィールドキーを格納せず、それを検索しません;)。
struct
、C++/Java class
などの他の言語の静的な側面を模倣することもできます。ローカルと配列で十分です。
テーブルを短くしておくと、テーブルが大きくなるほど検索時間が長くなります。また、同じ行で、数値インデックス付きのテーブル(=配列)を反復処理する方が、キーベースのテーブルよりも高速です(したがって、ipairはペアよりも高速です)