スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

--.--.-- | スポンサー広告

[Lua] Lua5.1のGC実行効率に関する考察

先日リリースされたLua5.1ですが、インクリメンタルGCが特徴のひとつとなっています。

GC(ガーベージコレクション)は、不要になったメモリを回収し、解放する機構のことですが、これが結構時間のかかる動作で、最近Luaを使った自作システムのプロファイルをAMD CodeAnalystで取ってみたら実行時間のほとんどがGCにかかっていた・・・ということもありました。基本的には動作の早いLuaだけに、GCがある種のネックともなっています。

Lua5.1では、インクリメンタルGCによって、Lua5.0までは一括で全メモリに対して実行するしかなかったGCを、細切れに実行することができるようになりました。特にゲームなどの半リアルタイム用途のアプリにおいて、GCが実行される時に突然大きく負荷がかかることを防止することができます。

・・・というのが口上ではありますが、しかし、そもそもGCってどうなの?どういう特性を持っているの?というとなかなかよくわからないものです。

また、Lua5.1ではcollectgarbageでいくつかパラメータを設定できますが、どういう値を設定すればいいのか、なかなか手がかりがないものと思います。

そこでいろいろ実験してデータを取ってみました。


注意)この実験では、ガーベージとして長めの文字列を使用したため、ガーベージが溜まることによる悪影響が現実的な場合の数十倍程度大きく表れている可能性があります。詳細はこちらの記事でご確認ください。


基本的な実験方法として、以下のようなコードで、そこそこの大きさの有効データをつくり、さらに大量のゴミデータを継続的に作り出します。この方法が「正しい」かどうかはわかりませんが、とりあえず変化を観察することができました。

ちなみにあまりGCへの造詣は深くありません(--;;



local t = {1,2,3,4,5}

-- 有効データ部分作成
-- t.x = table2 , table2.x = table3 , table3.x = table4 ....
-- というようにテーブルがネストしている構造
local p = t
for i=0,10000 do
local t2 = {[tostring(i).."abcdefghijklmnopqrstuvwxyz"]=i}
p.x = t2
p = t2
end

-- ゴミデータ作成
local interval = 200 -- GC実行間隔
local threshold = 2800 -- GC実行閾値
for i=0,50000 do
-- ゴミ文字列をくっつけて大きめのデータを作る
local text = tostring(i)..[[;eofih;afowicmaafcloaj,
wcf;wtcopiaujwetpcmoahwfgmpciauhwefmpcfhcaiouhcfg
maiouefhmcpoaihwefmcpoaihcfm;oaiwtcm;oaiuwerm;oci
auwermc;auiopwe,r;cwuioefmco;awuio;iweuxm;oaixmoai
whfmx;aoiwhfxam;oifhmax;oif]]

-- 最大100項目が生存するようにする
t[math.mod(i,100)] = { [text] = i }


-- interval回に1回collectgarbage実行
if math.mod(i,interval) == 0 then
collectgarbage(threshold)
end
-- 1000回に一回状態表示
if math.mod(i,1000) == 0 then
local a,b = gcinfo()
io.write("["..i.."|"..a.."]")
end
end

print( "\nclock="..os.clock())


まずはLua5.0の場合です。

Lua GCテスト グラフ1


intervalが2000以下、thresholdが3000以下であればそこそこの速度が出ていることがわかります。
その一方で、intervalが2000を超えたり、thresholdが3000を超えたりすると急激にパフォーマンスが落ちています。おおよそゴミが1MB以上貯まると加速度的に速度が落ちていくようです。

intervalが200以下になるとthresholdの低い場合(つまり毎回GCが動作する)は、GCの実行回数によるオーバーヘッドが顕著になってきます。
(GCが実行された後にgcinfo()で使用中メモリ容量を見ると2450(KB)となっていました。)

また、ゴミがちょっと出たところですぐ回収するよう、thresholdを2700~2800程度とするとintervalが低くても高いパフォーマンスが出ており、threshold2800,interval500で最大のパフォーマンスが得られています。

ここからわかることは・・・
・ゴミデータが増えるとGCの実行時間が加速度的に増えていく。
・collectgarbage()が呼ばれないうちはthresholdにかかわらずゴミはどんどん貯まっていってしまう
・thresholdは実際の使用メモリより1割~2割ほど多いあたりが良い。
・あまりGCの実行回数が多いと時間を食ってしまう。
・collectgarbage()が呼ばれてもthreshold判定によってGCが動かない場合はGC1回分の時間のロスはない。



さて、同様のことをLua5.1で行ってみました。
比較のため、折れ線グラフにしました。

collectgarbage(threshold)のかわりに、
Lua5.1ではcollectgarbage("collect")またはcollectgarbage("step", arg)を使用しています。

Lua GCテスト グラフ2


collectgarbage("collect")の場合は Lua5.0での threshold=0 の場合に近いことがわかります。
collectgarbage("step", arg)を使用した場合は、やや動作がかわってきますが、
arg=1000の場合は"collect"の場合と特性が似ています。GCがほぼフル動作したと考えて良さそうです。
arg=100の場合は、interval=100の場合の負荷が下がっており、Lua5.0 threshold=2800の場合と近いデータが出ています。ただし興味深いのは、interval=2000の場合に妙に時間がかかっている点です。
arg=1の場合はarg=100の場合に近いですが、全般的にやや時間がかかってしまっています。
また、グラフからはわかりにくいですが、step arg=1000以下では、argとintervalがおおよそ同じ程度の値のとき、最大のパフォーマンスが得られる傾向があります。


ここからわかることとしては・・・
・step arg=100程度でstep実行を高い頻度で行えば、GCの高頻度実行による負荷を回避しつつ、GC実行インターバルを適切に設定した場合に近いパフォーマンスを得ることができる。
・step arg=100以下では頻度が低すぎる場合パフォーマンスが下がる場合がある
・step argを低くする場合は、その分collectgarbageを呼ぶ頻度を高くすると良い。



さて、本題に入ります(遅っ)

Lua5.1ではGCの実行状態を2つのパラメータで制御できます。
pausestep multiplier の2つです。
マニュアルによるとおおよそ以下のような感じです。

pause:新しいサイクルが始まるまでのウエイトをコントロールする。100以下であればノーウエイト、200であれば使用メモリが2倍に増えるまでウエイトする。

step multiplier:メモリ確保に対してのGCの速度をコントロールする。値が大きければ1ステップの処理量が大きくなる。100以下であれば非常に遅くなり、サイクルを完了できないかもしれない。デフォルトの200ではメモリ確保の2倍の速度でGCが動きます。

これらは以下のようにcollectgarbage関数で設定できます。


collectgarbage("setpause",80) -- pause設定
collectgarbage("setstepmul",200) -- step multiplier設定


でも結局のところ、いったいどのような値にすればいいんでしょうか?
いろいろ値を変えて実行してみれば、なにか見えてくるかもしれませんよね?

実験に使ったコードでは、上の項目で実行していたコードからintervalごとにcollectgarbage()を呼ぶ部分を外してあります。
このため以下のようになっています。


local t = {1,2,3,4,5}

-- make some not-trash part
-- t.x = table2 , table2.x = table3 , table3.x = table4 ....
local p = t
for i=0,10000 do
local t2 = {[tostring(i).."abcdefghijklmnopqrstuvwxyz"]=i}
p.x = t2
p = t2
end

-- make trash
local interval = 1000
collectgarbage("setpause",80)
collectgarbage("setstepmul",120)
for i=0,50000 do
-- some heavy trash
local text = tostring(i)..[[;eofih;afowicmaafcloaj,
wcf;wtcopiaujwetpcmoahwfgmpciauhwefmpcfhcaiouhcfg
maiouefhmcpoaihwefmcpoaihcfm;oaiwtcm;oaiuwerm;oci
auwermc;auiopwe,r;cwuioefmco;awuio;iweuxm;oaixmoai
whfmx;aoiwhfxam;oifhmax;oif]]

t[math.mod(i,100)] = { [text] = i } -- max 100 item alive

if math.mod(i,interval) == 0 then
--collectgarbage("step",1)
end
if math.mod(i,1000) == 0 then
local a = math.floor(collectgarbage("count"))
io.write("["..i.."|"..a.."]")
end
end

print( "\nclock="..os.clock())


Lua GCテスト グラフ3


このような結果になったわけですが、なかなか奇妙なグラフです。(上限を100(秒)にしましたので、それ以上の部分のグラフは切れています。)
まず、step mulの値を100より低くすると急激にパフォーマンスが落ちています。これはマニュアルどおりですね。また、pauseの値が高いとゴミが貯まってしまい、やはりパフォーマンスがガタ落ちします。
step mulの値が300以上のあたりから、ややパフォーマンスが落ちています。
これはGCの実行しすぎによるものと思われます。pauseによって間隔をあけてやれば高いパフォーマンスを得ることもできますが、高いパフォーマンスを得られる範囲はstep mulが100付近の場合に比べると小さくなっています。

このため・・・
・step mulの値は100~200の値で、かつpauseの値が「ゴミが貯まり過ぎない程度」である場合に最大のパフォーマンスが得られる
・pauseの値が低いとパフォーマンスはやや下がるが、ゴミが貯まるとパフォーマンスが急激に悪化するので、実使用上は余裕を持って低めに設定したほうが良いと思われる。pause=0でも特に問題ということはない。
・pauseの値を高めに設定したい場合は、ゴミが貯まった場合には強制的に排除するよう、以下のようなコードで監視を行ったほうが良いと思われる。


-- グローバル変数 GC実行閾値初期化
gc_threshold = 1000


-- 以下はループ内などで実行
if collectgarbage("count") > gc_threshold then
collectgarbage("step",2000)
gc_threshold = collectgarbage("count") + 250
print( "threshold set to "..gc_threshold)
else
gc_threshold = gc_threshold - 1
end




全体まとめ

・Lua5.0からLua5.1でGCの効率自体はそんなに変わっていないが、GCの実行を細分化できるようになっている。

・Lua5.1では、pauseとstep multiplierの設定が合っていれば、collectgarbage関数を直接呼ばなくても高いパフォーマンスを実現できる。

・あまりゴミを貯めると大変なことになるので、基本的にはこまめにGCを実行したほうが良い

・最大のパフォーマンスを得られるのはGCをあまり余計に動かさない場合だが、ゴミを抱えてしまう危険と隣り合わせ。危険を回避するためには、使用メモリ容量の監視などを併用したほうが良い。

その他

・Lua5.1では、同じプログラムでもLua5.0と比べて使用メモリ(GC関数で見られるもの)が3割ぐらい少ない

・マニュアルのミス: collectgarbageの引数 "setpause" のはずが "steppause" と書かれている

・step mulの値を0や500以上にして、使用メモリの多い環境でGCをかけるとフリーズのような状態になる



以上です。
あーつかれた。
スポンサーサイト

テーマ:プログラミング - ジャンル:コンピュータ

2006.03.06 | Comments(0) | Trackback(0) | Lua

コメント

コメントの投稿


秘密にする

新しい記事へ <<  | HOME |  >> 古い記事へ

広告:

FC2Ad

カテゴリ展開メニュー

  • 未分類(13)
  • Lua(38)
  • プログラミング(11)
  • 食べ物(3)
  • SPAM(2)
  • ゲーム開発(4)
  • GIS/GPS/GoogleMaps(2)
  • スポーツ(1)
  • Skype API(1)
  • AR(1)

はてブ ランキング

ブログ全体: このWikiのはてなブックマーク数

プロフィール

はむ!

Author:はむ!
よく使う言語・環境:
C++,C,Lua,java,VBA,DB
たまにPHPとかjavascript
血液型:O型

メール: lua%ham.nifty.jp
(%を@に変えてください)
ついったー: @hammmm

Lua関連アンテナ

ブロとも申請フォーム

この人とブロともになる

全記事表示リンク

全ての記事を表示する

ブログ内検索


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。