Class::MOP::Class->get_method_map()のXS版
Class::MOP::Class->get_method_map()書きました。add_method()は入れてません。
ベンチマーク(5.8.9 on linux, without threads, -DDEBUGGING):
Initialization: Rate pp xs pp 2595/s -- -18% xs 3169/s 22% -- Looking into the stash: Rate pp xs pp 21920/s -- -73% xs 81919/s 274% -- Getting the cache: Rate pp xs pp 22755/s -- -98% xs 973306/s 4177% --
ベンチマークによれば,初期化はほとんど差がないので起動が劇的に早くなるわけではなさそう。ただ,ちょっとした変更があった場合のシンボルテーブルの走査(Looking into the stash)はそこそこ早く,何の変更も無かった場合の単純な参照(Getting the cache)は劇的に速いので,XSで書く意義はそれなりにあるんじゃないかな。
--- MOP.xs.orig 2008-11-15 06:37:06.000000000 +0900 +++ MOP.xs 2008-11-19 16:25:34.000000000 +0900 @@ -1,3 +1,4 @@ +#define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" @@ -19,6 +20,128 @@ SV *key_body; U32 hash_body; +SV* s_method_metaclass; +SV* s_associated_metaclass; +SV* s_wrap; + +#define check_package_cache_flag(stash) mop_check_package_cache_flag(aTHX_ stash) +static UV +mop_check_package_cache_flag(pTHX_ HV* stash){ /* equivalent to mro::get_pkg_gen($pkg) */ + PERL_UNUSED_ARG(stash); + assert(SvTYPE(stash) == SVt_PVHV); +#ifdef HvMROMETA +# ifndef mro_meta_init +# define mro_meta_init(stash) Perl_mro_meta_init(aTHX_ stash) +# endif + + assert(HvMROMETA(stash)); + return HvMROMETA(stash)->pkg_gen; +#else + return PL_sub_generation; +#endif +} + + +#define call0(s, m) mop_call0(aTHX_ s, m) +#define call0s(s, m) mop_call0(aTHX_ s, sv_2mortal(newSVpvs(m))); +static SV* +mop_call0(pTHX_ SV* const self, SV* const method){ + dSP; + SV* ret; + + PUSHMARK(SP); + XPUSHs(self); + PUTBACK; + + call_sv(method, G_SCALAR | G_METHOD); + + SPAGAIN; + ret = POPs; + PUTBACK; + + return ret; +} + +static void +mop_update_method_map(pTHX_ SV* const self, SV* const class_name, HV* const stash, HV* const map){ + const char* const class_name_pv = HvNAME(stash); /* must be HvNAME(stash), not SvPV_nolen_const(class_name) */ + SV* method_metaclass = NULL; + char* name; + I32 namelen; + GV* gv; + dSP; + dXSTARG; /* used by PUSHp() macro */ + + hv_iterinit(stash); + while((gv = (GV*)hv_iternextsv(stash, &name, &namelen))){ + CV* cv; + if(SvROK(gv)){ + /* gv_init() enbodies a constant and calls mro_method_changed_in(stash) */ + gv_init((GV*)gv, stash, name, namelen, GV_ADDMULTI); + } + + if(SvTYPE(gv) == SVt_PVGV && (cv = GvCVu(gv))){ + GV* const cvgv = CvGV(cv); + const char* const cvpkg_name = HvNAME(GvSTASH(cvgv)); + const char* const cv_name = GvNAME(cvgv); + SV* method_slot; + SV* method_object; + + /* skip if the code does not come from this package */ + if(!(strEQ(cvpkg_name, "constant") && strEQ(cv_name, "__ANON__"))){ + if(strNE(cvpkg_name, class_name_pv)){ + continue; + } + } + + method_slot = *hv_fetch(map, name, namelen, TRUE); + if(SvOK(method_slot)){ + SV* body = call0(method_slot, key_body); /* $method_object->body() */ + if(SvROK(body) && ((CV*)SvRV(body)) == cv){ + continue; + } + } + + if(!method_metaclass){ + method_metaclass = call0(self, s_method_metaclass); /* $self->method_metaclass() */ + } + + /* + $method_object = $method_metaclass->wrap( + $cv, + associated_metaclass => $self, + package_name => $class_name, + name => $method_name + ); + */ + ENTER; + SAVETMPS; + + PUSHMARK(SP); + EXTEND(SP, 8); + PUSHs(method_metaclass); /* invocant */ + mPUSHs(newRV_inc((SV*)cv)); + PUSHs(s_associated_metaclass); + PUSHs(self); + PUSHs(key_package_name); + PUSHs(class_name); + PUSHs(key_name); + PUSHp(name, namelen); /* use TARG */ + PUTBACK; + + call_sv(s_wrap, G_SCALAR | G_METHOD); + SPAGAIN; + method_object = POPs; + PUTBACK; + /* $map->{$method_name} = $method_object */ + sv_setsv(method_slot, method_object); + + FREETMPS; + LEAVE; + } + } +} + /* get_code_info: Pass in a coderef, returns: @@ -28,6 +151,8 @@ MODULE = Class::MOP PACKAGE = Class::MOP +PROTOTYPES: DISABLE + BOOT: key_name = newSVpvs("name"); key_body = newSVpvs("body"); @@ -38,7 +163,9 @@ PERL_HASH(hash_body, "body", 4); PERL_HASH(hash_package, "package", 7); PERL_HASH(hash_package_name, "package_name", 12); - + s_method_metaclass = newSVpvs("method_metaclass"); + s_wrap = newSVpvs("wrap"); + s_associated_metaclass = newSVpvs("associated_metaclass"); PROTOTYPES: ENABLE @@ -244,3 +371,45 @@ XPUSHs(HeVAL(he)); else ST(0) = &PL_sv_undef; + +MODULE = Class::MOP PACKAGE = Class::MOP::Class + +SV* +get_method_map(self) + SV* self +INIT: + if(!SvRV(self)){ + die("Cannot call get_method_map as a class method"); + } +CODE: +{ + HE* const he = hv_fetch_ent((HV*)SvRV(self), key_package, TRUE, hash_package); /* $self->name() */ + SV* const class_name = HeVAL(he); + HV* const stash = gv_stashsv(class_name, TRUE); + UV const current = check_package_cache_flag(stash); + SV* const cache_flag = *hv_fetchs((HV*)SvRV(self), "_package_cache_flag", TRUE); + SV* const map_ref = *hv_fetchs((HV*)SvRV(self), "methods", TRUE); + + if(SvROK(map_ref) && SvTYPE(SvRV(map_ref)) == SVt_PVHV){ + RETVAL = map_ref; + SvREFCNT_inc_simple_void_NN(RETVAL); + } + else{ + RETVAL = newRV_noinc((SV*)newHV()); + sv_setsv(map_ref, RETVAL); + } + + if(!(SvOK(cache_flag) && SvUV(cache_flag) == current)){ + ENTER; + SAVETMPS; + + mop_update_method_map(aTHX_ self, class_name, stash, (HV*)SvRV(map_ref)); + sv_setuv(cache_flag, check_package_cache_flag(stash)); /* update_cache_flag() */ + + FREETMPS; + LEAVE; + } +} +OUTPUT: + RETVAL +
#!perl -w use strict; use Class::MOP::Class; BEGIN{ *Class::MOP::Class::pp_get_method_map = \&Class::MOP::Class::get_method_map } use Class::MOP; use Benchmark qw(:all); { package Foo; use metaclass; sub bar{42}; foreach my $n(1 .. 10){ no strict 'refs'; *{'baz' . $n} = \&bar; } } print "Initialization:\n"; my $meta = Foo->meta; cmpthese -1 => { xs => sub{ $meta->reset_package_cache_flag; %{$meta->{methods}} = (); my $map = $meta->get_method_map(); }, pp => sub{ $meta->reset_package_cache_flag; %{$meta->{methods}} = (); my $map = $meta->pp_get_method_map(); }, }; print "\n", "Looking into the stash:\n"; cmpthese -1 => { xs => sub{ $meta->reset_package_cache_flag; my $map = $meta->get_method_map(); }, pp => sub{ $meta->reset_package_cache_flag; my $map = $meta->pp_get_method_map(); }, }; print "\n", "Getting the cache:\n"; cmpthese -1 => { xs => sub{ my $map = $meta->get_method_map(); }, pp => sub{ my $map = $meta->pp_get_method_map(); }, };