tips: perl スクリプトが使うメモリ量を調べる方法

追記: ymko さんが 、より汎用性の高い方法を【 [プロセスが使ったメモリー量を調べる](http://qiita.com/ymko/items/de0ee7b6607fc7892d5d) 】で紹介してくれています。perl 環境に限定されない、より優れた手法です。

本記事は [Memory::Usage の使い方](
http://qiita.com/bass_ikeda/items/aae7f287984b0e25f2ca#%E8%80%83%E5%AF%9F) のところを参照していただければ良いと思います。

----

perl スクリプトがどのくらいメモリを使用するか、をざっくり調べるには

```pl
END { system("/bin/ps -o pid,vsz,args -e -q $$ >&2"); }
```

をスクリプト内に仕込むと、スクリプト終了時に使用しているメモリ量を表示できます。

DEBUG フラグを併用して

```pl
use constant DEBUG => 1;

END { if(DEBUG) { system("/bin/ps -o pid,vsz,args -e -q $$ >&2"); } }
```

などとすると良いでしょう。


# 環境

CentOS 6.5
perl 5.10.1
procps 3.2.8

# 手法

こんなスクリプトを書いて

```pl:sample.pl
#!/usr/bin/perl

use strict;
use warnings;
use utf8;

use constant DEBUG => 1;

END { if(DEBUG) { system("/bin/ps -o pid,vsz,args -e -q $$ >&2"); } }

# some code here ...
```

実行すると、スクリプト終了時に END ブロック内の ps コマンドが実行されて

```sh
$ ./sample.pl
  PID    VSZ COMMAND
31605  26724 /usr/bin/perl ./sample.pl
$
```

といった出力が標準エラー出力に得られます。
VSZ の単位は KiB (=1024 byte) です。詳しくは man ps を参照して下さい。

出力が不要になったら DEBUG を 0 にする…のは言わずもがなです。

# 注意点

ps に指定するオプション `-q $$` で、出力エントリを perl スクリプト自身に限定しています。

つまり `$$` が perl スクリプト自身の PID に展開される必要があります。
なので system() の引数は `""` で括ります。

シングルクォーテーション(')にすると、perl スクリプト内で `$$` は展開されず、system() に渡された後に shell で展開されます。
結果、出力されるのは、system() が起動した shell のメモリ使用量になってしまいます。これは欲しかったものではありません。

```sh
$ diff sample.pl fail.pl
9c9
< END { if(DEBUG) { system("/bin/ps -o pid,vsz,args -e -q $$ >&2"); } }
---
> END { if(DEBUG) { system('/bin/ps -o pid,vsz,args -e -q $$ >&2'); } }
$
$ ./fail.pl
  PID    VSZ COMMAND
31690   9204 sh -c /bin/ps -o pid,vsz,args -e -q $$ >&2
$
```

# 考察

END ブロック で `ps` を実行するこの手法は、スクリプトを実行した際に結局どのくらいのメモリが必要だったか、を調べるために使えます。
『動的に確保(＝malloc())したメモリは、解放(＝free())してもシステムには返却されず、再利用のためプロセスが終了するまで割り当てられる』ため、スクリプト終了時にプロセスに割り当てられていたメモリ量は、スクリプト実行に必要だったメモリ量と解釈できます。

malloc() および Perl のメモリ管理については、CPAN: [Devel::Size / Nicholas Clark, 2005](http://search.cpan.org/~nwclark/Devel-Size-0.80/lib/Devel/Size.pm) に簡単な記述があり、参考になります。

スクリプト全体でなく、スクリプト内の特定処理がどのくらいメモリを食うか、を調べるには [Memory::Usage](
http://search.cpan.org/~doneill/Memory-Usage-0.201/lib/Memory/Usage.pm) が使えます。
スクリプト内の所々でメモリ使用状況をサンプリング(record)して、一覧表示(dump)して比較する、みたいな使い方をします。

ただし、[Memory::Usage](
http://search.cpan.org/~doneill/Memory-Usage-0.201/lib/Memory/Usage.pm) は標準ではインストールされていない[^1]ため、CPAN から追加インストールして下さい。

[^1]: CentOS 6.5 環境で確認しました。

以下のようにして使います[^2]。

[^2]: コードは [How much memory does my Perl application use?](https://perlmaven.com/how-much-memory-does-the-perl-application-use) / Gabor Szabo に記載のものを参考にしました。ありがとうございます。 

``` pl:mem-usage.pl
#!/usr/bin/perl

use strict;
use warnings;
use utf8;

use Memory::Usage;

my $mu = Memory::Usage->new();
$mu->record('starting work');   # SAMPLING

# my real code
my $a = "a" x 65536;

$mu->record('after creating variable'); # SAMPLING

$mu->dump();
```

こんな出力が得られます。

```sh
$ ./mem-usage.pl
  time    vsz (  diff)    rss (  diff) shared (  diff)   code (  diff)   data (  diff)
     0  25792 ( 25792)   2232 (  2232)   1648 (  1648)      8 (     8)    776 (   776) starting work
     0  25964 (   172)   2380 (   148)   1668 (    20)      8 (     0)    948 (   172) after creating variable
$
```

cf. [How much memory does my Perl application use?](https://perlmaven.com/how-much-memory-does-the-perl-application-use) / Gabor Szabo


# 結論

perl スクリプトのメモリ使用量をざっくり調べるには

```pl
use constant DEBUG => 1;

END { if(DEBUG) { system("/bin/ps -o pid,vsz,args -e -q $$ >&2"); } }
```

とすると、スクリプト終了時に使用しているメモリ量を表示します。

スクリプト内の特定処理がどのくらいメモリを食うか、を調べるには [Memory::Usage](
http://search.cpan.org/~doneill/Memory-Usage-0.201/lib/Memory/Usage.pm) が使えます。

# 参考文献

- How much memory does my Perl application use? / Gabor Szabo, https://perlmaven.com/how-much-memory-does-the-perl-application-use
- CPAN: Memory::Usage / David O'Neill, 2010     http://search.cpan.org/~doneill/Memory-Usage-0.201/lib/Memory/Usage.pm
- CPAN: Devel::Size / Nicholas Clark, 2005      http://search.cpan.org/~nwclark/Devel-Size-0.80/lib/Devel/Size.pm
- man ps
- プロセスが使ったメモリー量を調べる / ymko, 2017	http://qiita.com/ymko/items/de0ee7b6607fc7892d5d