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();
	},
};