Designing for Scalability with Elrang/OTPより
- Erlangのプロセスには、heap sizeが割り当てられている
- このheapサイズを使い切るとガベージコレクションが稼動する
- Erlang GCはtwo generationalで、まずyoung heapからold heapに移す。そして新しいyoung heapをつくって新しいデータを保管する。old heapにデータを移したyoung heapの領域は開放される
- もしyoung heapをクリーンできない場合、あるいはyoung heapを移すための領域が確保できない場合はFull-sweep GCが発生する。これは、young heapといっしょに参照されていないold heapもクリーンする
- full-sweep GCの後もメモリが確保できない場合はallocating memory chanksを増やす。chunksのサイズはフィボナッチ数列に基づいて徐々に増やしていく
- full-sweep GCは、genarational GCが特定回数発生した後にも定期的に実行される。プロセスがメモリを大量に確保しきった後に、プロセスは生きているけどデータは参照されないという状況もあるため。
- プロセスの状態がprocess heapのみに格納されるわけではない
- 64bytes以上のbinaryはshared heapに格納される。各プロセスはメッセージパッシングを使ってshared heapにアクセスする
- 参照カウンタがあり、これが0になるとGCが発生する
- process localのvirtual binary heapというものもある。これは各heapの領域がいっぱいになり、領域の開放が必要になったらGCが発生する
- 64bytes以下のbinaryはprocces local のnormal heapに保持される。これは、他のプロセスにメッセージとして送信される場合やGCの間にvirtual binary heapにコピーされる
- local heapとlocal virtual heapのGCはプロセスごとに発生する。リアルタイム処理をメモリ管理のために中断されるということがない
process heap
- heapのサイズはspawn_optで設定可能。gen_serverもstart_linkでこのオプションをとれる
- プロセスが終了したらheapは開放される
min_heap_size
- ここで指定するサイズはwords。プロセッサのアーキテクチャによって変わる。32ビットアーキテクチャの場合は 1 word = 4 bytes = 32 bits。64ビットアーキテクチャの場合は 1 word = 8 bytes = 64 bits。
- デフォルト値は下記のような感じ
- min_heap_size が 233 なので、1864 bytes = 約 1.9kb 初期heapが確保される
1> erlang:system_flag(min_heap_size, 0)
1> .
233
2> erlang:process_info(self()).
[{current_function,{erl_eval,do_apply,6}},
{initial_call,{erlang,apply,2}},
{status,running},
{message_queue_len,0},
{messages,[]},
{links,[<0.57.0>]},
{dictionary,[]},
{trap_exit,false},
{error_handler,error_handler},
{priority,normal},
{group_leader,<0.53.0>},
{total_heap_size,752},
{heap_size,376},
{stack_size,24},
{reductions,2134},
{garbage_collection,[{max_heap_size,#{error_logger => true,kill => true,size => 0}},
{min_bin_vheap_size,46422},
{min_heap_size,233},
{fullsweep_after,65535},
{minor_gcs,10}]},
{suspending,[]}]
private heap GC
軽量プロセスのprivate GCについては以下のブログ記事が解説してくれていた。
Erlang Garbage Collection Details and Why It Matters - Hamidreza Soleimani’s Blog
以下は、上記記事のまとめ+Designing…からのメモ。
1. Spawn > No GC > Terminate
- min_heap_sizeに達しないうちにプロセスが終了した場合、その終了したプロセスのメモリは回収される
2. Spawn > Fullsweep > Generational > Terminate
- 新しくspawnされたプロセスのデータがmin_heap_sizeに達し、まだold-generation, young-generation に全体のheap領域に分かれていない場合は、Full GCが発生する
- 初めてFull GCが発生したら、heapはyoung/oldに分かれる
- この後、GCのstrategyはgenerational GCとなる
- プロセスのデータは、プロセス終了までold領域に確保された状態になる
3. Spawn > Fullsweep > Generational > Fullsweep > Generational > … > Terminate
- Generation sweepからFull sweepにstrategyが変わる場合がある:
- globalか、プロセスごとに
fullsweep_after
フラグを設定できる。 http://erlang.org/doc/man/erlang.htmde- fullsweep前にgenerational sweepを最大何回実行するかを指定することができる
- この設定値をゼロにすると、old heapに空きがあってもgeneral collection algorithm(generation sweep?)をオフにすることができる。つまり full sweep が発生してyoung, old領域のデータすべてが開放される
- Erlangのドキュメントによると、
fullsweep_after
が有用になるのは以下の場合とのこと:- バイナリが使われない場合。すぐに捨ててよい場合(
fullsweep_after
をゼロにする)
- バイナリが使われない場合。すぐに捨ててよい場合(
- プロセスが短期間しか参照しないデータを持っていて、それがたまにか、あるいはまったくfullsweepされない場合。こういった場合はold heapはgarbageを持っていることになる。fullsweep を時々起こすには適切な値を設定する。例えば10や20など。
fullsweep_after
のデフォルト値は 65535 (Designing for Scalability with Erlang より。公式ドキュメントからは見つけられなかった)。つまり65535回 generational sweep が発生しなければ、old領域が開放されない
- globalか、プロセスごとに
4. Spawn > Fullsweep > Generational > Fullsweep > Increase Heap > Fullsweep > … > Terminate
- 2回目の fullsweep でGCが十分なメモリを確保できなかった場合、軽量プロセスの heap size は増えて fullsweep が再度実行される
- 新規に軽量プロセスを起動したのと同じような挙動。2のように fullsweep 後に young, old の世代領域に分かれて、 genarational sweepが発生する
- 生きている軽量プロセスにおいて、1…4 の sweep が繰り返し実行される
参考リンク
TODO
- Erlang in Anger内でのトピックス
- hibernateについて追記