7

Luaスクリプトコードのパフォーマンスを向上させるためのさまざまなテクニックを説明しているドキュメントを探していますが、そのようなトリックが必要になることにショックを受けました。(私はLuaを引用していますが、Javascriptで同様のハッキングを見てきました)。

この最適化が必要な理由:

たとえば、コード

for i = 1, 1000000 do 
   local x = math.sin(i) 
end

これよりも30%遅く実行されます:

local sin = math.sin 
for i = 1, 1000000 do
    local x = sin(i) 
end

sinそれらはローカルで関数を再宣言しています。

なぜこれが役立つのでしょうか?とにかくそれを行うのはコンパイラの仕事です。なぜプログラマーはコンパイラーの仕事をしなければならないのですか?

私はJavascriptで同様のことを見てきました。したがって、明らかに、解釈コンパイラがその仕事をしていないという非常に正当な理由があるに違いありません。それは何ですか?


私がいじっているLua環境でそれを繰り返し見ます。変数をローカルとして再宣言する人々:

local strfind = strfind
local strlen = strlen
local gsub = gsub
local pairs = pairs
local ipairs = ipairs
local type = type
local tinsert = tinsert
local tremove = tremove
local unpack = unpack
local max = max
local min = min
local floor = floor
local ceil = ceil
local loadstring = loadstring
local tostring = tostring
local setmetatable = setmetatable
local getmetatable = getmetatable
local format = format
local sin = math.sin

人々がコンパイラの仕事をしなければならないということは、ここで何が起こっているのでしょうか?コンパイラは検索方法に混乱していますformatか?なぜこれはプログラマーが対処しなければならない問題なのですか?なぜこれは1993年に処理されなかったのでしょうか?


私も論理的なパラドックスにぶつかったようです。

  1. プロファイリングなしで最適化を行うべきではありません
  2. ルアにはプロファイリングする能力がありません
  3. Luaは最適化されるべきではありません
4

6 に答える 6

35

なぜこれが役立つのでしょうか?とにかくそれを行うのはコンパイラの仕事です。なぜプログラマーはコンパイラーの仕事をしなければならないのですか?

Luaは動的言語です。コンパイラーは、定数式をループから引き出すなど、静的言語で多くの推論を行うことができます。動的言語では、状況は少し異なります。

Luaの主な(そして唯一の)データ構造はテーブルです。mathここでは名前空間として使用されていますが、これも単なるテーブルです。ループのどこかで関数を変更するのを止めることはできmath.sinません(それを行うのは賢明ではないとしても)。コンパイラーは、コードをコンパイルするときにそれを知ることができません。したがって、コンパイラーは、ユーザーが指示したとおりに実行します。ループのすべての反復でsin、テーブル内の関数を検索してmath呼び出します。

これで、変更しないことがわかっている場合math.sin(つまり、同じ関数を呼び出す場合)、ループ外のローカル変数に保存できます。テーブルルックアップがないため、結果のコードは高速になります。

LuaJITでは状況が少し異なります。トレースと高度な魔法を使用して、コードが実行時に何をしているかを確認します。そのため、実際にコンパイルする以外に、式をループの外に移動することでループを実際に最適化できます。それを機械語に変換し、それを速く狂わせます。

'変数をローカルとして再宣言する'に関して-モジュールを定義するときに何度も、元の関数を使用する必要があります。pairs、またはそれらのグローバル変数を使用して何かにアクセスするときmax、誰もそれがすべての呼び出しで同じ関数になることを保証できません。たとえば、stdlibは多くのグローバル関数を再定義します。

グローバルと同じ名前のローカル変数を作成することにより、基本的に関数をローカル変数に格納します。ローカル変数(字句スコープ、つまり現在のスコープとネストされたスコープでも表示される)が優先されるため、グローバルでは、常に同じ関数を呼び出すようにしてください。後で誰かがグローバルを変更しても、モジュールには影響しません。グローバルはグローバルテーブル()で検索されるため、言うまでもなく高速_Gです。

更新:Luaの作者の1人であるRobertoIerusalimschyによるLuaPerformance Tipsを読みました。これは、Lua、パフォーマンス、および最適化について知っておく必要のあるすべてのことをほぼ説明しています。IMOの最も重要なルールは次のとおりです。

ルール#1:それをしないでください。

ルール2:まだやらないでください。(専門家のみ)

于 2011-01-10T12:45:56.207 に答える
11

デフォルトで行われない理由はわかりません。ただし、ローカルがレジスタに書き込まれるのに対し、グローバルはテーブル(_G)で検索することを意味しますが、これはやや遅いことが知られています。

可視性について(format関数のように):ローカルはグローバルを覆い隠します。したがって、グローバルと同じ名前のローカル関数を宣言すると、スコープ内にある限り、代わりにローカルが使用されます。代わりにグローバル関数を使用する場合は、_G.functionを使用してください。

本当に速いLuaが必要な場合は、 LuaJITを試すことができます

于 2011-01-10T09:24:23.637 に答える
9

私がいじっているLua環境でそれを繰り返し見ます。変数をローカルとして再宣言する人々:

デフォルトでそれを行うことは明らかに間違っています。

例のループ内のように、関数が何度も使用される場合は、テーブルアクセスの代わりにローカル参照を使用すると間違いなく便利です。

local sin = math.sin 
for i = 1, 1000000 do
  local x = sin(i) 
end

ただし、ループの外側では、テーブルアクセスを追加するオーバーヘッドは完全に無視できます。

人々がコンパイラの仕事をしなければならないということは、ここで何が起こっているのでしょうか?

上記で作成した2つのコードサンプルは、まったく同じことを意味するわけではないためです。

関数の実行中に関数が変更されるわけではありません。

Luaは非常に動的な言語であり、Cなどの他のより制限の厳しい言語と同じ仮定をすることはできません。ループの実行中に関数が変更される可能性があります。言語の動的な性質を考えると、コンパイラーは関数が変更されないと想定することはできません。または、少なくとも、コードとその影響の複雑な分析なしではありません。

秘訣は、2つのコードが同等に見えても、Luaではそうではないということです。最初のものでは、「すべての反復で数学テーブル内にsin関数を取得する」ように明示的に指示しています。2つ目では、同じ関数への単一の参照を何度も使用しています。

このことを考慮:

-- The first 500000 will be sines, the rest will be cosines
for i = 1, 1000000 do 
   local x = math.sin(i)
   if i==500000 then math.sin = math.cos end 
end

-- All will be sines, even if math.sin is changed
local sin = math.sin
for i = 1, 1000000 do 
   local x = sin(i)
   if i==500000 then math.sin = math.cos end 
end
于 2011-01-10T16:04:39.880 に答える
3

ローカル変数に関数を格納すると、ループの反復ごとにファンクションキーを検索するためのテーブルインデックスが削除されます。数学テーブルでハッシュを検索する必要があるため、数学のものは明らかです。その他はそうではなく、インデックスに登録されます。_G(グローバルテーブル)、_ENV5.2では(環境テーブル)になりました。

また、デバッグフックAPIを使用するか、または周りにあるluaデバッガーを使用して、luaのプロファイルを作成できる必要があります。

于 2011-01-10T05:18:21.640 に答える
1

私の仮定では、最適化されたバージョンでは、関数への参照がローカル変数に格納されているため、forループの反復ごとにツリートラバーサルを実行する必要はありません(へのルックアップの場合math.sin)。

関数名に設定されたローカル参照についてはわかりませんが、ローカルの名前空間が見つからない場合は、何らかのグローバル名前空間の検索が必要になると思います。

それからまた、私はベースから離れている可能性があります;)

編集:私はまた、Luaコンパイラーがばかげていると思います(これはとにかくコンパイラーについての私にとっての一般的な仮定です;))

于 2011-01-10T05:10:10.053 に答える
1

これは単なるバグ/機能ではありません。クラスや配列などの範囲外の値ではなくローカル値にアクセスするとLua、多くの言語がより高速に実行されます。JavaC

C++たとえば、あるクラスの変数メンバーにアクセスするよりも、ローカルメンバーにアクセスする方が高速です。

これは10,000に速くカウントされます:

for(int i = 0; i < 10000, i++)
{
}

よりも:

for(myClass.i = 0; myClass.i < 10000; myClass.i++)
{
}

テーブル内にグローバル値を保持する理由Luaは、プログラマーが_Gが参照するテーブルを変更するだけで、グローバル環境をすばやく保存および変更できるためです。グローバルテーブル_Gを特殊なケースとして扱った「シンタティックシュガー」があればいいのにと思います。それらをすべてファイルスコープ(または同様のもの)のローカル変数として書き換えます。もちろん、これを自分で行うことを妨げるものは何もありません。おそらく、関数optGlobalEnv(...)は、unpack()などを使用して、_Gテーブルとそのメンバー/値を「ファイルスコープ」に「ローカライズ」します。

于 2012-11-08T12:28:15.550 に答える