my TYPE EXPR;という書式を初めて知ったので調べた。
追記
御叱り元
Re: use fields; と my TYPE EXPR; - Islands in the byte stream
fields::new()が遅いからですね。fields::new()の意味は、5.10.0より前のバージョンとの互換性のためと、実行時チェックのためのHash::Util::lock_keys()の適用です。5.10.0以上限定で実行時チェックが不要なら使う必要はありません。
field::newが遅いだけでした!
ベンチマークも取り直してみました。
use fields;
fieldsを使うと、フィールドに不正な代入があったら、"コンパイル時"にはじくことが出来る。
ハッシュキーをよくtypoする人にはおすすめだが、実行速度は遅い。
AUTOLOADを使って動的にgetter/setterを作って実行時エラーを出すようにしても、fieldsはそれ以上に遅い。
ただ、ハッシュをblessしたものとそっくり置き換えることが可能なので、
開発中はfields, リリース時は高速なハッシュをblessしたものを使うようにすると良いかもしれない...?
Hash::Utilのrestricted hashesの選択肢もあるが、また別のお話。
my TYPE EXPR;
動的型付けなのに、TYPEとか意味あるのか、とか思っていたが、fieldsと一緒に使うとかなんとか。
スーパークラスを実装したサブクラスであれば使えるよ、と明示するためのものといった印象。
package Hoge; use strict; use warnings; use fields qw(XXX); sub new{ my $class = shift; my $self = fields::new($class); return $self; } package Fuga; use strict; use warnings; use base qw(Hoge); use fields qw(YYY); sub new{ my $class = shift; my $self = fields::new($class); $self->SUPER::new; return $self; } package main; my Hoge $hoge = new Hoge(); $hoge->{XXX} = 100; # OK # $hoge->{YYY} = 100; # Hogeでは未定義のフィールドなのでNG my Fuga $fuga = new Fuga(); $fuga->{XXX} = 100; # OK $fuga->{YYY} = 100; # OK my Hoge $hogefuga = $fuga; # アップキャスト的な感じ $hogefuga->{XXX} = 100; # OK # $hogefuga->{YYY} = 100; # TYPE==Hoge のため、FugaのフィールドであるYYYへアクセスや変更はできない。
bless {},__PACKAGE__;とuse fields;とAUTOLOADを使った3種類のgetter/setterのベンチマーク
#!/usr/bin/perl package Hoge; use strict; use warnings; use fields qw(XXX YYY); sub new{ my Hoge $class = shift; return fields::new($class); } package Fuga; use strict; use warnings; sub new{ my Fuga $class = shift; return bless { XXX => 0, YYY => 0 }, $class; } package Moge; use strict; use warnings; sub new{ my Moge $class = shift; return bless { XXX => 0, YYY => 0 }, $class; } sub DESTROY{ } sub AUTOLOAD{ my $self = shift; our $AUTOLOAD; my $method = $AUTOLOAD; $method =~s/.+:://; if(exists $self->{$method}){ eval qq{ sub ${method}{my \$s=shift; \$s->{$method} = shift if \@_; return \$s->{$method};} }; return $self->$method(@_); } die "Error"; } package main; use strict; use warnings; use Benchmark qw(timethese) ; timethese(1000000, { 'use fields' => sub{ my $obj = new Hoge(); $obj->{XXX} = 100; $obj->{YYY} = 200; my $x = $obj->{XXX}; my $y = $obj->{YYY}; die if $x * 2 != $y; }, 'sub AUTOLOAD' => sub{ my $obj = new Moge(); $obj->XXX(100); $obj->YYY(200); my $x = $obj->XXX; my $y = $obj->YYY; die if $x * 2 != $y; }, 'bless hash' => sub{ my $obj = new Fuga(); $obj->{XXX} = 100 if exists $obj->{XXX};; $obj->{YYY} = 200 if exists $obj->{YYY}; my $x = $obj->{XXX} if exists $obj->{XXX}; my $y = $obj->{YYY} if exists $obj->{YYY}; die if $x * 2 != $y; } });
Benchmark: timing 1000000 iterations of bless hash, sub AUTOLOAD, use fields...
bless hash: 3 wallclock secs ( 2.64 usr + 0.00 sys = 2.64 CPU) @ 379218.81/s
(n=1000000)
sub AUTOLOAD: 5 wallclock secs ( 4.82 usr + 0.00 sys = 4.82 CPU) @ 207468.88/
s (n=1000000)
use fields: 12 wallclock secs (13.52 usr + 0.00 sys = 13.52 CPU) @ 73937.15/s (
n=1000000)
ぜんぜん違いますね。
再計測(インスタンス生成は1回のみ)
#!/usr/bin/perl package Hoge; use strict; use warnings; use fields qw(XXX YYY); sub new{ my Hoge $class = shift; return fields::new($class); } package Fuga; use strict; use warnings; sub new{ my Fuga $class = shift; return bless { XXX => 0, YYY => 0 }, $class; } package Moge; use strict; use warnings; sub new{ my Moge $class = shift; return bless { XXX => 0, YYY => 0 }, $class; } sub DESTROY{ } sub AUTOLOAD{ my $self = shift; our $AUTOLOAD; my $method = $AUTOLOAD; $method =~s/.+:://; if(exists $self->{$method}){ eval qq{ sub ${method}{my \$s=shift; \$s->{$method} = shift if \@_; return \$s->{$method};} }; return $self->$method(@_); } die "Error"; } package main; use strict; use warnings; use Benchmark qw(timethese) ; my $f = new Fuga(); my $m = new Moge(); my $h = new Hoge(); timethese(1000000, { 'use fields' => sub{ $h->{XXX} = 100; $h->{YYY} = 200; my $x = $h->{XXX}; my $y = $h->{YYY}; die if $x * 2 != $y; }, 'sub AUTOLOAD' => sub{ $m->XXX(100); $m->YYY(200); my $x = $m->XXX; my $y = $m->YYY; die if $x * 2 != $y; }, 'bless hash' => sub{ $f->{XXX} = 100 if exists $f->{XXX}; $f->{YYY} = 200 if exists $f->{YYY}; my $x = $f->{XXX} if exists $f->{XXX}; my $y = $f->{YYY} if exists $f->{YYY}; die if $x * 2 != $y; } });
Benchmark: timing 1000000 iterations of bless hash, sub AUTOLOAD, use fields...
bless hash: 1 wallclock secs ( 1.14 usr + 0.00 sys = 1.14 CPU) @ 877963.13/s (n=1000000)
sub AUTOLOAD: 2 wallclock secs ( 2.89 usr + 0.00 sys = 2.89 CPU) @ 346500.35/s (n=1000000)
use fields: 1 wallclock secs ( 0.72 usr + 0.00 sys = 0.72 CPU) @ 1392757.66/s (n=1000000)
ぜんぜん違いますね!!