web-dev-qa-db-ja.com

LuaJIT2最適化ガイド

LuaJIT 2 用にLuaコードを最適化する方法についての良いガイドを探しています。コンパイルされているトレースとコンパイルされていないトレースを検出する方法など、LJ2の詳細に焦点を当てる必要があります。

ポインタはありますか? Lua MLの投稿へのリンクのコレクションが答えになります(これらのリンクをここに要約するためのボーナスポイント)。

更新:タイトルテキストを「プロファイリング」から「最適化」ガイドに変更しました。これはより理にかなっています。

28

更新

Mikeは最近、LuaJIT用の素晴らしい軽量プロファイラーを作成してリリースしました。それを見つけることができます ここ

更新

ウィキはこの分野でさらにいくつかのページを獲得しました。特に これ 、元の回答に記載されていないいくつかの追加事項を詳しく説明し、 メーリングリストの投稿 に基づいていますマイクによる。


LuaJITはごく最近、独自の wiki および メーリングリスト を立ち上げました。そのようなものとともに、LuaJITのコードの高速化に関する多くの宝石が登場します。

現在、ウィキはかなり薄いです(ただし、常にウィキに追加する人を探しています)が、最近追加された1つのすばらしいページは NYI関数のリスト です。 NYI関数を使用すると、JITがベイルアウトしてインタプリタにフォールバックするため、ホットパス、特にループでは、NYI関数をできるだけ避ける必要があります。

メーリングリストからの関心のあるいくつかのトピック:

そして、さらに下で述べたことを繰り返すだけで(それは非常に役立つため)、-jvはパフォーマンスチューニングに最適なツールであり、トラブルシューティングの最初の手段にもなります。


元の回答

主にLJ2がまだベータ版であるため、これについて多くのことがわかるとは思えません。したがって、トレースレコーダーのようなLJ2固有のもののデバッグフックがないため、ほとんどのプロファイルは単純に実行されます。

プラス面として、新しいFFIモジュールでは、高解像度タイマー(またはVTune/CodeAnalystなどのプロファイリングAPI)を直接呼び出すことができます。そのようにプロファイリングできますが、それ以上の場合はLJ2JITコアの拡張が必要です。 、コードが明確でコメントされているため。


トレースレコーダーのコマンドラインパラメーターの1つ( ここ から取得):

-jvおよび-jdumpコマンドは、Luaで記述された拡張モジュールです。これらは主に、JITコンパイラ自体のデバッグに使用されます。オプションと出力形式の説明については、ソースの冒頭にあるコメントブロックをお読みください。これらは、ソースディストリビューションのlibディレクトリにあるか、jitディレクトリの下にインストールされています。デフォルトでは、これはPOSIXシステムでは/usr/local/share/luajit-2.0.0-beta8/jitです。

つまり、コマンドのモジュールコードを使用して、LuaJIT2のプロファイリングモジュールのベースを形成できます。


更新

質問の更新により、これは少し答えやすくなります。それでは、ソースから始めましょう LuaJIT.org

コードを手動で最適化する前に、JITの最適化チューニングリソースを確認することをお勧めします。

コンパイル

Running ページから、JITのパラメーターを設定するためのすべてのオプションを確認できます。最適化のために、-Oオプションに焦点を当てます。すぐにMikeは、すべての最適化を有効にしてもパフォーマンスへの影響は最小限であると言ったので、必ず-O3(現在はデフォルト)で実行してください。したがって、ここで実際に価値のあるオプションはJITとTraceのしきい値だけです。

これらのオプションは、作成しているコードに非常に固有であるため、デフォルト以外に一般的な「最適な設定」はありませんが、言うまでもなく、コードに多くのループがある場合は、ループの展開を試して実行時間を計ってください(ただし、コールドスタートのパフォーマンスを求めている場合は、実行ごとにキャッシュをフラッシュしてください)。

-jvは、JITが救済される原因となる知識 issues/'fallbacks' を回避するのにも役立ちます。

サイト自体は、 FFIチュートリアル :のいくつかの小さなヒントを除いて、より良いまたはより最適化されたコードを書く方法について多くを提供していません。

関数キャッシング

関数のキャッシュはLuaの優れたパフォーマンスブースターですが、LuaJITで焦点を当てることはそれほど重要ではありません。これは、JITがこれらの最適化のほとんどを自分で行うため、isに注意することが重要です。 FFI C関数のキャッシュはbadであり、それらが存在する名前空間をキャッシュすることをお勧めします。

ページからの例:

悪い:

local funca, funcb = ffi.C.funcb, ffi.C.funcb -- Not helpful!
local function foo(x, n)
  for i=1,n do funcb(funca(x, i), 1) end
end

良い:

local C = ffi.C          -- Instead use this!
local function foo(x, n)
  for i=1,n do C.funcb(C.funca(x, i), 1) end
end

FFIパフォーマンスの問題

Status セクションでは、コードのパフォーマンスを低下させるさまざまな構造と操作について詳しく説明します(主にコンパイルされていないためですが、代わりにVMフォールバックを使用します)。

ここで、すべてのLuaJITgemのtheソースに移動します Luaメーリングリスト

  • ループ内でのC呼び出しとNYI Lua呼び出しの回避 :LJ2トレーサーを起動して有用なフィードバックを提供する場合は、トレースコンパイラでNYI(まだ実装されていない)関数またはC呼び出しを回避する必要があります。行くことができません。したがって、luaにインポートしてループで使用できる小さなC呼び出しがある場合は、それらをインポートします。最悪の場合、Cコンパイラの実装よりも「6%遅く」、せいぜい速くなります。

  • ipairsで線形配列を使用 :Mikeによると、pairs/nextは、他の方法と比較して常に遅くなります(トレーサーのシンボルキャッシュについては、ちょっとした情報もあります)。

  • ネストされたループを避ける :各ネストレベルはトレースするために余分なパスを取り、わずかに最適化されません。特に、反復が少ない内部ループを避けます。

  • ベースの配列を使用できます :マイクはここで、標準のLuaとは異なり、LuaJITには0ベースの配列に対するパフォーマンスの低下がないと述べています。

  • 可能な限り最も内側のスコープでローカルを宣言する :理由の本当の説明はありませんが、IIRCはこれがSSAの活気分析と関係があります。また、あまりにも多くの地元の人々を避ける方法に関するいくつかの興味深い情報が含まれています(これは活気の分析を壊します)。

  • 多くの小さなループを避けてください :これは展開するヒューリスティックを台無しにし、コードの速度を低下させます。

小さな一口:

プロファイリングツールは通常のLuaで利用できますが、LuaJITと公式に互換性のある新しいプロジェクトが1つあります(ただし、LuaJITの機能のいずれかが考慮されるとは思えません) luatrace 。 Lua wikiには 最適化のヒント のページもあります。通常のLuaの場合、これらはLuaJITでの有効性をテストする必要があります(これらの最適化のほとんどはおそらくすでに内部で実行されています)が、LuaJITは引き続きデフォルトのGCの場合、手動による最適化の向上が依然として大きい可能性がある1つの領域として残ります(マイクがあちこちで行っていると述べたカスタムGCを追加するまで)。

LuaJITのソースには、JITの内部をいじるためのいくつかの設定が含まれていますが、これらは特定のコードに合わせて調整するために広範なテストが必要になります。実際、特にそうでない人にとっては、完全に回避する方がよい場合があります。 JITの内部に精通している。

31
Necrolis

あなたが探しているものではありませんが、私はjit。*トレース機能のリバースエンジニアリングを管理しました。以下は、少しラフで不正確で、変更される可能性があり、非常に不完全です。 luatrace ですぐに利用を開始します。これらの関数は、いくつかの-jライブラリファイルで使用されます。 _dump.lua_はおそらく始めるのに良い場所です。

jit.attach

_jit.attach_を使用して、多数のコンパイライベントにコールバックをアタッチできます。コールバックは次のように呼び出すことができます。

  • 関数がバイトコード( "bc")にコンパイルされたとき。
  • トレース記録が開始または停止したとき(「トレース」)。
  • トレースが記録されているとき(「レコード」)。
  • または、トレースがサイド出口( "texit")から出る場合。

jit.attach(callback, "event")でコールバックを設定し、jit.attach(callback)で同じコールバックをクリアします

コールバックに渡される引数は、報告されているイベントによって異なります。

  • "bc":callback(func)funcは、記録されたばかりの関数です。
  • 「トレース」:callback(what, tr, func, pc, otr, oex)
    • whatは、トレースイベントの説明です: "flush"、 "start"、 "stop"、 "abort"。すべてのイベントで利用できます。
    • trはトレース番号です。フラッシュには使用できません。
    • funcは、トレースされる関数です。開始と中止に使用できます。
    • pcはプログラムカウンターです-記録されている関数のバイトコード番号(これがLua関数の場合)。開始と中止に使用できます。
    • otr start:これがサイドトレースの場合は親トレース番号、abort:コードの中止(整数)?
    • oex start:親トレースの終了番号、abort:中止理由(文字列)
  • 「レコード」:callback(tr, func, pc, depth)。最初の引数は、トレース開始の場合と同じです。 depthは、現在のバイトコードのインライン化の深さです。
  • "texit":callback(tr, ex, ngpr, nfpr)
    • trは以前と同じトレース番号です
    • exは出口番号です
    • ngprおよびnfprは、出口でアクティブな汎用および浮動小数点レジスターの数です。

jit.util.funcinfo(func、pc)

_jit.attach_コールバックからfuncpcが渡されると、_jit.util.funcinfo_は、_debug.getinfo_と同様に、関数に関する情報のテーブルを返します。

テーブルのフィールドは次のとおりです。

  • linedefined:_debug.getinfo_について
  • lastlinedefined:_debug.getinfo_について
  • params:関数が取るパラメーターの数
  • stackslots:関数のローカル変数が使用するスタックスロットの数
  • upvalues:関数が使用するアップバリューの数
  • bytecodes:コンパイルされた関数のバイトコード数
  • gcconsts:??
  • nconsts:??
  • currentline:_debug.getinfo_について
  • isvararg:関数がvararg関数の場合 `
  • source:_debug.getinfo_について
  • loc: "<source>:<line>"のように、ソースと現在の行を説明する文字列
  • ffid:関数の高速関数ID(存在する場合)。この場合、上記のupvaluesと以下のaddrのみが有効です。
  • addr:関数のアドレス(Lua関数でない場合)。高速関数ではなくC関数の場合、上記のupvaluesのみが有効です。
7
Geoff Leyland

私は過去に ProFi を使用しましたが、かなり便利でした。

2
tommitytom