生のハッシュリファレンスを return したら遅い
ハッシュリファレンスは畳み込み的に定数扱いじゃなかったのね、ってのに気づいて驚いた(というのが本当に遅い原因か確定させて無いけど)(【追記2】参照)。
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw/timethese cmpthese/;
my $HASH = {
foo => 123,
bar => 456,
baz => 789,
};
my $result = timethese (750_000, {
'VAL' => '&logic1;',
'RAW' => '&logic2;',
});
cmpthese $result;
sub logic1 { _logic1()->{foo}; }
sub _logic1 {
return $HASH;
}
sub logic2 { _logic2()->{foo}; }
sub _logic2 {
return {
foo => 123,
bar => 456,
baz => 789,
};
}
# Rate RAW VAL
# RAW 590551/s -- -58%
# VAL 1415094/s 140% --
【追記】
@lestrrat さんが以下のエントリで詳しく説明してくれました。
Perlでconstant foldingされるのは文字列・数値リテラルか、定数扱いできる関数だけです。
定数扱いできる関数というのは実は決まっていて、以下の条件がそろわないとconstant folding されない:
1. その関数は 引数を取らない、とprototypeで明示的に宣言してある
2. その関数は文字列・数値リテラルを返し、それ以外の処理を行わないquoted from: Perlでは何が定数なのか - D-6 [相変わらず根無し]
リファレンスも「constant プラグマを使えばオッケー!」だそうです。確かに、constant プラグマ版でベンチとってみると速度が戻りました(CON が constatn)。
# Rate RAW CON VAL
# RAW 543478/s -- -60% -61%
# CON 1363636/s 151% -- -2%
# VAL 1388889/s 156% 2% --
なるほど!
#!/usr/bin/perl
use strict;
use warnings;
use constant const_hash => {
foo => 123,
bar => 456,
baz => 789
};
use Benchmark qw/timethese cmpthese/;
my $HASH = {
foo => 123,
bar => 456,
baz => 789,
};
my $result = timethese (750_000, {
'VAL' => '&logic1;',
'RAW' => '&logic2;',
'CON' => '&logic3;',
});
cmpthese $result;
sub logic1 { _logic1()->{foo}; }
sub _logic1 {
return $HASH;
}
sub logic2 { _logic2()->{foo}; }
sub _logic2 {
return {
foo => 123,
bar => 456,
baz => 789,
};
}
sub logic3 { _logic3()->{foo}; }
sub _logic3 {
return const_hash;
}
【追記2】
遅さの原因について、検証方法をブクマで教えていただきました。
Craftworks _logic2 の方は毎回メモリ確保する分遅いです。返ってきたリファレンスのアドレス確認すると毎回違うはずです。この違い意識してないとリファレンスの中身書き換えるときに意図した動作と違ってハマります。
ああ、なるほど、確かにそうだ。
というわけで、試してみる。
$ perl -le 'print foo() for (1..5); sub foo { return +{ bar => 123, baz => 456 } }'
HASH(0x86d3d3c)
HASH(0x86d4660)
HASH(0x86d3d6c)
HASH(0x86d3d3c)
HASH(0x86d4660)
アドレス自体は再利用されてるっぽい。
$ perl -le 'print foo() for (1..10000); sub foo { return +{ bar => 123, baz => 456 } }' | sort | uniq -c | sort -r
3334 HASH(0x93a2d3c)
3333 HASH(0x93a3660)
3333 HASH(0x93a2d6c)
おそらく、参照の中身は毎回定義されてて、たまたまこのワンライナーの場合は3つのアドレスを使いまわして割り当てされてるんでしょうね。
まあとりあえず、return +{};
な書き方はしない方がいい、と。