Perl は幼児レベルしかわからないので、今日も人に質問されてわからなかった。
- 「サブルーチンに文字列を渡した後、サブルーチン内で substr を使って部分文字列を取得しようとしています。この場合、文字列をそのままサブルーチンに渡すのではなくリファレンスを渡した方が、値のコピーを防げるために性能面で有利だと聞いたのですが、本当ですか。substr に渡すためにデリファレンスする時点でどうせ値がコピーされてしまうのでしょうから、たいして変わらないのではないですか」
以下、帰宅してから Mac の 5.12.4 で試したメモ。作業記録であって結論はない。
こういう場合に Perl で使われる標準的な技法というのがあるんだろうけど、知らないので原始的なデバッグ文に頼ってみることにした。
リファレンスを使わない場合
まず、やろうとしていることは、こんな感じ。
#!/usr/bin/perl sub foo { my ($s) = @_; print substr($s, 100, 5), "\n"; } $a = "hello" x (1024 * 1024 * 100); &foo($a);
これに、print と sleep を適宜混ぜ込んだ。
#!/usr/bin/perl sub foo { my ($s) = @_; print "my\n"; sleep 5; print substr($s, 100, 5), "\n"; print "print\n"; sleep 5; } $a = "hello" x (1024 * 1024 * 100); print "a\n"; sleep 5; &foo($a); print "return\n"; sleep 5;
実行すると、以下のように、print される。sleep も入れたので、とりあえず今どこまで進んだのかは確認できる。
$ ./noref.pl a my hello print return $
ここで、Perl を実行するのとは別に、もうひとつウィンドウを開いて top を実行し、何をやった時点でどれだけメモリを消費しているかを調べた。
僕の事前の予想はこうだった。
どこまでやったか | メモリ消費 | なぜそう思うか |
---|---|---|
$a = "hello" x (1024 * 1024 * 100); | 500MB | 文字列の長さが 500 * 1024 * 1024 だから |
my ($s) = @_; | 1000MB | Perl ではサブルーチンの引数が参照渡し (変数渡し) だけど my を使うとローカル変数を作れると聞いたから |
print substr($s, 100, 5), "\n"; | 1500MB | 関数に渡す前に値がコピーされそうだから |
ところが、実際にやってみると、いきなり予想と違っていた。
どこまでやったか | メモリ消費 |
---|---|
$a = "hello" x (1024 * 1024 * 100); | 1000MB |
my ($s) = @_; | 1500MB |
print substr($s, 100, 5), "\n"; | 1500MB |
文字列を作って変数に代入した時点ですでに文字列 2 つ分のメモリが使われている。これはなんでだろう。
そして、サブルーチン内のローカル変数に値をコピーしたところで +500MB なのは予想通りだったけど、substr しているところではメモリが増えなかった (値がコピーされなかった) ように見える。ただ、top は一定期間ごとに表示を更新する仕組みだから、実は値はコピーされたんだけど、一瞬なので top には映らなかっただけ、という可能性もある。
うむ、よくわからん。
リファレンスを使ってみる
サブルーチンに渡すのをリファレンスにして、substr に渡す直前でデリファレンスしてみる。
#!/usr/bin/perl sub foo { my ($s) = @_; print "my\n"; sleep 5; print substr($$s, 100, 5), "\n"; print "print\n"; sleep 5; } $a = "hello" x (1024 * 1024 * 100); print "a\n"; sleep 5; &foo(\$a); print "return\n"; sleep 5;
再度 top で確認すると、
どこまでやったか | メモリ消費 |
---|---|
$a = "hello" x (1024 * 1024 * 100); | 1000MB |
my ($s) = @_; | 1000MB |
print substr($$s, 100, 5), "\n"; | 1000MB |
リファレンスを渡しているから、ローカル変数へのコピーでは参照 1 つ分しかメモリ消費が増えないのはわかる。
substr の行でメモリ消費が増えたのか減ったのかが、やはりわからない。増えていないように見えるが、top だけではなんとも……。