2024/02/13 22:13:39

CoW 要点まとめ

共有メモリを上手につかってアプリのパフォーマンスを向上させる

CoW とは

CoW = Copy-On-Write

[ CoW - Wikipedia ]

プロセスのコピーを作成 (fork) するとき、書き換えることのないメモリページは、両方のプロセス(元のプロセスと生成されたプロセス)で共用し、書き換える可能性のあるメモリページは、新たなメモリページを割り当ててコピーを作成する

鉄則

fork するプロセスを生成する場合、親子間でメモリ共有した方が良い

  1. forkする前に確保したメモリ領域は子プロセスとの間で共有される
  2. 共有されたメモリ領域に対して書き込みを行うと共有は解除(コピー)される
  3. forkした後に確保したメモリ領域は子プロセスごとに確保される(共有されない)

どのプロセスでも同じように参照するだけの(書き換えない)データは、fork前に確保して共有した方が全体でのメモリ使用量を小さく保てる。つまり、fork 数を多くしたりできる。そして、処理数を上げることができる。

メモリ共有の確認

あるプロセスのメモリマッピング状態を見るには cat /proc/$PID/smaps | head すれば良い

  • Shared_Clean と Shared_Dirty の合計が共有メモリ量
  • Private_* の項目がそのプロセスだけのメモリ
  • Perl だと Linux::Smaps が便利
  • シンプルに子プロセスのなかで ref $foo してみてそれぞれアドレスが同じか見る

Plack App の場合

  • Plack アプリなら、master プロセスで全部 use したり require したりする
  • 遅延ロードされるものも親で use するべし

遅延ロードされるモジュールを調べる

Plack アプリの場合
  • Plack::Middleware::Debug::LazyLoadModules を使って遅延ロードされるモジュールを見つける
  • たとえば、starman なら --preload-app オプションを活用する
    • 個人的には -M で全部根こそぎ読むようにしている
mod_perl の場合

以下のようなモジュールを利用する

package INCdiff;
# 親で init しておいて、ケツで handler 呼ぶ
use strict;
use Apache::Constants qw(OK);
use vars qw(%init_module);

sub init { $init_module{$_}++ for keys %INC; }

sub handler {
    my @required = grep { !$init_module{$_} } keys %INC;
    warn "-------- module diff ---------";
    warn join "\n", @required;
    return OK;
    }
1;

TIPS

starman の CoW っぷりを調べるスクリプト

実行結果はこんな感じ

    $ ./check_starman_cow.pl
    PID     TYPE    RSS     SHARED
    3418    master  17732   13692 (77%)
    3419    worker  20736   12996 (62%)
    3420    worker  20740   13000 (62%)
    3421    worker  20736   12996 (62%)
  • Perl モジュールの個別メモリ使用量は GTop で調べられる

SEE ALSO

サイト内検索