32

Lua のパフォーマンスについて質問したところ、次のような回答がありまし

Lua のパフォーマンスを高く保つための一般的なヒントを調べましたか? つまり、テーブルの作成を知っており、新しいテーブルを作成するよりもむしろテーブルを再利用し、'local print=print' などを使用してグローバル アクセスを回避します。

これはLua Patterns,Tips and Tricks とは少し異なる質問です。具体的にパフォーマンスに影響を与える回答と、(可能であれば) パフォーマンスが影響を受ける理由の説明が欲しいからです。

回答ごとに 1 つのヒントが理想的です。

4

5 に答える 5

79

他の回答とコメントのいくつかに応えて:

プログラマーとして時期尚早な最適化を避けるべきであることは事実です。しかし。これは、コンパイラがあまり最適化しない、またはまったく最適化しないスクリプト言語には当てはまりません。

そのため、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__lenipairs

関数table.insert,table.remove

table.insertandの単純な使用法は、代わりに演算子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 つではなく、単一の値のサニティ チェックのみを行う必要があることがわかります。差分計算も抽出しましたが、おそらくあまり改善されていませんが、このパターンが達成しようとしていることを示していることを願っています.

于 2012-10-12T18:57:36.920 に答える
10

lua プログラムが本当に遅すぎる場合は、Lua プロファイラーを使用して高価なものをクリーンアップするか、C に移行します。しかし、待っていないと、時間が無駄になります。

最適化の第一法則: やらないこと。

ipairs とpairs のどちらかを選択し、その違いの影響を測定できる問題を見てみたいです。

簡単に達成できる簡単な方法の 1 つは、各モジュール内でローカル変数を使用することを忘れないことです。一般的に、次のようなことをする価値はありません

local strfind = string.find

そうでないことを示す測定値が見つからない限り。

于 2008-11-28T21:15:21.697 に答える
4
  • 最もよく使われる関数をローカルにする
  • テーブルを HashSet として活用する
  • 再利用によるテーブル作成の削減
  • ルアジットを使って!
于 2008-11-28T02:06:51.467 に答える
1

テーブルを短くしてください。テーブルが大きいほど、検索時間が長くなります。そして、同じ行で、数値的にインデックス付けされたテーブル (= 配列) を反復することは、キー ベースのテーブルよりも高速です (したがって、ipairs はペアよりも高速です)。

于 2008-11-28T01:40:52.250 に答える