他の回答とコメントのいくつかに応えて:
プログラマーとして時期尚早な最適化を避けるべきであることは事実です。しかし。これは、コンパイラがあまり最適化しない、またはまったく最適化しないスクリプト言語には当てはまりません。
そのため、Lua で何かを記述し、それが非常に頻繁に実行される場合、タイム クリティカルな環境で実行される場合、またはしばらく実行される可能性がある場合は、回避すべきこと(および回避すること)を知っておくことをお勧めします。
これは、私が時間の経過とともに見つけたことのコレクションです。一部はネットで見つけましたが、インターウェブに関しては怪しいので、すべて自分でテストしました。また、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 は作成時にすべての文字列をハッシュします。これにより、テーブルでの比較と使用が非常に高速になり、すべての文字列が一度だけ内部に保存されるため、メモリの使用量が削減されます。ただし、文字列の作成はより高価になります。
過剰な文字列の作成を避けるための一般的なオプションは、テーブルを使用することです。たとえば、長い文字列を組み立てる必要がある場合は、テーブルを作成し、そこに個々の文字列を入れてから、一度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 は 2 番目の文字列を作成します"AAAAAA..."
(サイズによって異なりますC
)。の代わりに使用できますが、多くの場合、そのような便利なループがなく、使用できる反復子変数がないことに注意してください。i
#ret+1
lua-users.org のどこかで見つけたもう 1 つのトリックは、文字列を解析する必要がある場合に gsub を使用することです。
some_string:gsub(".", function(m)
return "A";
end);
これは最初は奇妙に見えますが、利点は、gsub が C で「一度に」文字列を作成し、gsub が返されたときに lua に返された後にのみハッシュされることです。これにより、テーブルの作成が回避されますが、関数のオーバーヘッドが増える可能性があります (foo()
とにかく呼び出す場合ではなく、 iffoo()
が実際に式である場合)
関数のオーバーヘッドを避ける
可能であれば、関数の代わりに言語構造を使用する
関数ipairs
テーブルを反復するとき、ipairs からの関数のオーバーヘッドはその使用を正当化しません。テーブルを反復するには、代わりに使用します
for k=1, #tbl do local v = tbl[k];
関数呼び出しのオーバーヘッドなしでまったく同じことを行います (pairs は実際には、テーブル内のすべての要素に対して呼び出される別の関数を返しますが、#tbl
一度しか評価されません)。値が必要な場合でも、はるかに高速です。そうしないと...
Lua 5.2 に関する注意__ipairs
: 5.2では、実際にメタテーブルでフィールドを定義できるため、場合によっては便利です。ipairs
ただし、Lua 5.2 では__len
テーブルのフィールドも機能するため、メタメソッドが 1 回だけ呼び出されるため、上記のコードを好むかもしれませんが、反復ごとに追加の関数呼び出しが発生します。ipairs
__len
ipairs
関数table.insert
,table.remove
table.insert
andの単純な使用法は、代わりに演算子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
また、ハッシュ テーブルのルックアップ時間は一定であるため、比較を追加するたびにパフォーマンスが向上します。一方、「ほとんどの場合」1 つまたは 2 つの比較が一致する場合は、ブール式または組み合わせを使用したほうがよい場合があります。
頻繁なテーブル作成を避ける
これについては、Lua Performance Tipsで詳しく説明しています。基本的に問題は、Lua が必要に応じてテーブルを割り当てることです。この方法で行うと、コンテンツを消去して再度埋めるよりも実際には時間がかかります。
pairs()
ただし、Lua 自体はテーブルからすべての要素を削除する方法を提供しておらず、それ自体がパフォーマンスの獣ではないため、これは少し問題です。この問題について、私自身はまだパフォーマンス テストを行っていません。
可能であれば、テーブルをクリアする C 関数を定義してください。これは、テーブルの再利用に適したソリューションです。
同じことの繰り返しは避ける
これが一番の問題だと思います。非インタープリター言語のコンパイラーは多くの冗長性を簡単に最適化できますが、Lua はそうしません。
メモ化
テーブルを使用すると、これは Lua で非常に簡単に実行できます。引数が 1 つの関数については、それらを table および __index メタメソッドに置き換えることもできます。これにより透明性が損なわれますが、関数呼び出しが 1 つ少ないため、キャッシュされた値のパフォーマンスが向上します。
これは、メタテーブルを使用した単一の引数のメモ化の実装です。(重要: このバリアントは 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];
実際には、複数の入力値に対してこのパターンを変更できます
この考え方は、結果を「キャッシュ」するメモ化に似ています。ただし、ここでは、関数の結果をキャッシュする代わりに、ブロック内の計算関数を定義するコンストラクター関数に計算を配置して、中間値をキャッシュします。実際には、クロージャーの巧妙な使用と呼んでいます。
-- 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));
ブレンダーが作成されると、関数は最大 8 つではなく、単一の値のサニティ チェックのみを行う必要があることがわかります。差分計算も抽出しましたが、おそらくあまり改善されていませんが、このパターンが達成しようとしていることを示していることを願っています.