Perlの最適化 - aelemfast

先の記事のOpcodeトレース(APVM版)に以下のようなOpcodeがあった。

.aelemfast[@ARGV[0]] SCALAR

これはPerlの最適化器*1が最適化の結果生成したOpcodeで,配列の添え字が定数かつ255以下のときにOpcodeのaelemから変換されるものだ。
もともとのaelemは,配列と添え字をスタックに積み,その二つの値を参照して配列要素を返すOpcodeである。これがaelemfastになると,一つのOpcodeに配列と添え字を共に保存するため,実行されるOpcodeの数は減り,スタックも使わなくなる。
細かい話はさておき,実際の効果を見てみる。
perl 5.10.0 linux-thread-multi, -DDEBUGGING:

            Rate     aelem aelemfast    scalar
aelem     3829/s        --      -37%      -41%
aelemfast 6108/s       60%        --       -5%
scalar    6461/s       69%        6%        --

perl 5.8.8 linux-thread-multi

            Rate     aelem aelemfast    scalar
aelem     5219/s        --      -20%      -32%
aelemfast 6516/s       25%        --      -15%
scalar    7657/s       47%       18%        --

aelemは$a[300],aelemfastは$a[100], scalarは比較のためのただのスカラー変数の参照である。
バージョンやビルドオプションによっても異なるが,この最適化が効くとスカラー変数の参照には劣るものの,通常の配列要素参照よりも25%から60%ほど高速になる。

これだけみると非常に素晴らしい。しかし,実際にはこの最適化を役立てるのは難しい。この最適化は配列を直接参照しないと効かず,リファレンスによる参照には適応されないからである。
これがリファレンス参照のときにも効くようになれば,オブジェクトを配列のリファレンスで実装することの優位性が高まると思われるのだが。

ベンチマークスクリプト

#!perl -w
use strict;
use Benchmark qw(:all);
our @x;
$x[100]++; # aelemfast
$x[300]++; # aelem
my $s;
$s++;      # scalar
printf "Benchmark (perl %vd, $^O)\n", $^V;
cmpthese -1 => {
	aelemfast => sub{
		for my $i (1 .. 1000){
			$x[100] = $i;
		}
	},
	scalar => sub{
		for my $i (1 .. 1000){
			$s = $i;
		}
	},
	aelem => sub{
		for my $i (1 .. 1000){
			$x[300] = $i;
		}
	},
};
__END__

*1:op.cのPerl_peep()