1. ホーム
  2. スクリプト・コラム
  3. ルア

Luaチュートリアル(XIII)。テーブルを弱く参照する

2022-02-13 06:33:15

Luaはガベージコレクションによるメモリ管理機構を採用しているので、プログラマにとってはメモリ問題が気にならない場合が多いでしょう。しかし、どんなガベージコレクタも万全ではなく、特殊なケースでは、ガベージコレクタが現在のオブジェクトをクリーンアップすべきかどうかを正確に判断できないことがあります。そのため、多くのガベージオブジェクトが解放されないという結果になる可能性が高い。この問題を解決するために、Luaの開発者はある程度協力する必要があります。例えば、あるテーブル・オブジェクトがコンテナに格納され、コンテナの外にそれを参照する変数がなくなった場合、Luaのガベージコレクタは、そのオブジェクトがまだコンテナ・オブジェクトによって参照されているため、オブジェクトをクリーンアップすることはありません。このコンテナの用途がルックアップに限定され、イテレーションが行われない場合、このオブジェクトが使用されることはありません。実際、このようなオブジェクトは、Luaのガベージコレクタがクリーンアップすることが期待されます。次のコードを見てください。

コピーコード コードは以下の通りです。

a = {}
key = {}
a[key] = 1
key = {}
a[key] = 2
collectgarbage()
for k,v in pairs(a) do
    print(v)
end   
--output 1 and 2

ガベージコレクションを実行した後、テーブルaのキーはどちらもクリーンアップできませんが、値が1に等しいキーについては、この後のロジックがテーブルaをトラバースしない場合、オブジェクトがメモリリークしていると仮定できます。Luaにはテーブルへの弱い参照というメカニズムがあり、上記のコードの最初のテーブルキーなどのオブジェクトは、ガベージコレクション実行時にテーブルへの弱い参照によってのみ参照されていればクリーンアップが促されるようになっています。

Luaの弱参照テーブルには、キーが弱参照、値が弱参照、キーと値の両方が弱参照という3つの弱参照モードがあります。弱参照テーブルの種類に関係なく、キーや値がリサイクルされると同時に、それらが含まれるエントリ全体がテーブルから削除されます。

テーブルの弱参照タイプは、そのメタ・テーブルの __mode フィールドによって決定されます。テーブルの値が "k" という文字を含む場合はキー弱参照、 "v" を含む場合は値、両方の文字が存在する場合はキー/値弱参照となります。次のコードを参照してください。

コピーコード コードは以下の通りです。

a = {}
b = {__mode = "k"}
setmetatable(a,b)
key = {}
a[key] = 1
key = {}
a[key] = 2
collectgarbage()
for k,v in pairs(a) do
    print(v)
end   
--just output 2

上のコード例では、最初のキーはテーブルaに格納された後、2番目のキーの定義によって上書きされるため、その唯一の参照先はキー弱参照テーブルからとなります。実はこれと同じ仕組みがJavaにもあり、1.5以降のバージョンではLuaの弱参照テーブルと同様のセマンティクスを持つ弱参照コンテナのセットが提供されています。

最後に、Luaの弱参照表はtable型の変数に対してのみ機能し、valuesやstringなど他の型の変数に対しては何もしないことに注意する必要があります。

1. メモライズ関数

    Space for time"は、プログラムの効率化を図るための一般的な最適化ツールです。例えば、Luaのコードを含むリクエストを受け取った通常のServerでは、リクエストを受け取るたびにLuaのloadstring関数を呼び出し、リクエスト内のLuaコードを動的にパースすることになります。この動作があまりに頻繁だと、Serverの実行効率の低下につながります。この問題を解決するために、各解析結果をテーブルにキャッシュしておけば、次回同じLuaコードを受信した際に、loadstirngを呼び出して動的に解析するのではなく、テーブルから解析済みの関数を取得して直接実行すれば良いのです。これにより、Luaコードの重複が多い場合、Serverの実行効率を大幅に向上させることができます。逆に、Luaコードのかなりの部分が一度しか登場しない場合、再度この仕組みを利用すると、効率的に解放されないまま大量のメモリリソースが占有されることになります。このような場合、弱い参照テーブルを使用すれば、プログラムの効率をある程度向上させることができるだけでなく、メモリ・リソースを効果的に解放することができます。次のコードをご覧ください。

コピーコード コードは以下の通りです。

local results = {}
setmetatable(results,{__mode = "v"}) -- the key in the results table is a Lua code in string form
function mem_loadstring(s)
    local res = results[s]
    if res == nil then
        res = assert(loadstring(s))
        results[s] = res
    end
    return res
end