Filename | /usr/share/perl/5.10/Memoize.pm |
Statements | Executed 741 statements in 5.02ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
29 | 1 | 1 | 1.99ms | 1.61s | _memoizer | Memoize::
5 | 2 | 2 | 907µs | 1.20ms | memoize | Memoize::
5 | 1 | 1 | 123µs | 135µs | _make_cref | Memoize::
10 | 1 | 1 | 52µs | 52µs | _my_tie | Memoize::
10 | 2 | 1 | 22µs | 22µs | CORE:match (opcode) | Memoize::
1 | 1 | 1 | 16µs | 62µs | BEGIN@27 | Memoize::
1 | 1 | 1 | 13µs | 18µs | BEGIN@293 | Memoize::
1 | 1 | 1 | 12µs | 17µs | BEGIN@228 | Memoize::
1 | 1 | 1 | 12µs | 28µs | BEGIN@30 | Memoize::
1 | 1 | 1 | 12µs | 16µs | BEGIN@330 | Memoize::
1 | 1 | 1 | 11µs | 29µs | BEGIN@28 | Memoize::
1 | 1 | 1 | 10µs | 14µs | BEGIN@97 | Memoize::
1 | 1 | 1 | 10µs | 13µs | BEGIN@34 | Memoize::
1 | 1 | 1 | 9µs | 31µs | BEGIN@29 | Memoize::
0 | 0 | 0 | 0s | 0s | _crap_out | Memoize::
0 | 0 | 0 | 0s | 0s | flush_cache | Memoize::
0 | 0 | 0 | 0s | 0s | unmemoize | Memoize::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # -*- mode: perl; perl-indent-level: 2; -*- | ||||
2 | # Memoize.pm | ||||
3 | # | ||||
4 | # Transparent memoization of idempotent functions | ||||
5 | # | ||||
6 | # Copyright 1998, 1999, 2000, 2001 M-J. Dominus. | ||||
7 | # You may copy and distribute this program under the | ||||
8 | # same terms as Perl itself. If in doubt, | ||||
9 | # write to mjd-perl-memoize+@plover.com for a license. | ||||
10 | # | ||||
11 | # Version 1.01 $Revision: 1.18 $ $Date: 2001/06/24 17:16:47 $ | ||||
12 | |||||
13 | package Memoize; | ||||
14 | 1 | 1µs | $VERSION = '1.01_03'; | ||
15 | |||||
16 | # Compile-time constants | ||||
17 | sub SCALAR () { 0 } | ||||
18 | sub LIST () { 1 } | ||||
19 | |||||
20 | |||||
21 | # | ||||
22 | # Usage memoize(functionname/ref, | ||||
23 | # { NORMALIZER => coderef, INSTALL => name, | ||||
24 | # LIST_CACHE => descriptor, SCALAR_CACHE => descriptor } | ||||
25 | # | ||||
26 | |||||
27 | 3 | 33µs | 2 | 109µs | # spent 62µs (16+47) within Memoize::BEGIN@27 which was called:
# once (16µs+47µs) by Memoize::Memcached::BEGIN@8 at line 27 # spent 62µs making 1 call to Memoize::BEGIN@27
# spent 47µs making 1 call to Exporter::import |
28 | 3 | 29µs | 2 | 47µs | # spent 29µs (11+18) within Memoize::BEGIN@28 which was called:
# once (11µs+18µs) by Memoize::Memcached::BEGIN@8 at line 28 # spent 29µs making 1 call to Memoize::BEGIN@28
# spent 18µs making 1 call to Exporter::import |
29 | 3 | 27µs | 2 | 54µs | # spent 31µs (9+22) within Memoize::BEGIN@29 which was called:
# once (9µs+22µs) by Memoize::Memcached::BEGIN@8 at line 29 # spent 31µs making 1 call to Memoize::BEGIN@29
# spent 22µs making 1 call to vars::import |
30 | 3 | 60µs | 2 | 45µs | # spent 28µs (12+17) within Memoize::BEGIN@30 which was called:
# once (12µs+17µs) by Memoize::Memcached::BEGIN@8 at line 30 # spent 28µs making 1 call to Memoize::BEGIN@30
# spent 17µs making 1 call to Config::import |
31 | 1 | 10µs | @ISA = qw(Exporter); | ||
32 | 1 | 700ns | @EXPORT = qw(memoize); | ||
33 | 1 | 700ns | @EXPORT_OK = qw(unmemoize flush_cache); | ||
34 | 3 | 248µs | 2 | 17µs | # spent 13µs (10+3) within Memoize::BEGIN@34 which was called:
# once (10µs+3µs) by Memoize::Memcached::BEGIN@8 at line 34 # spent 13µs making 1 call to Memoize::BEGIN@34
# spent 4µs making 1 call to strict::import |
35 | |||||
36 | 1 | 200ns | my %memotable; | ||
37 | 1 | 200ns | my %revmemotable; | ||
38 | 1 | 1µs | my @CONTEXT_TAGS = qw(MERGE TIE MEMORY FAULT HASH); | ||
39 | 1 | 6µs | my %IS_CACHE_TAG = map {($_ => 1)} @CONTEXT_TAGS; | ||
40 | |||||
41 | # Raise an error if the user tries to specify one of thesepackage as a | ||||
42 | # tie for LIST_CACHE | ||||
43 | |||||
44 | 1 | 4µs | my %scalar_only = map {($_ => 1)} qw(DB_File GDBM_File SDBM_File ODBM_File NDBM_File); | ||
45 | |||||
46 | # spent 1.20ms (907µs+292µs) within Memoize::memoize which was called 5 times, avg 240µs/call:
# 4 times (696µs+228µs) by C4::Reserves::BEGIN@27 or C4::Templates::BEGIN@35 at line 70 of Memoize/Memcached.pm, avg 231µs/call
# once (211µs+64µs) by C4::Biblio::BEGIN@33 at line 81 of /usr/share/koha/lib/C4/Koha.pm | ||||
47 | 253 | 518µs | my $fn = shift; | ||
48 | my %options = @_; | ||||
49 | my $options = \%options; | ||||
50 | |||||
51 | unless (defined($fn) && | ||||
52 | (ref $fn eq 'CODE' || ref $fn eq '')) { | ||||
53 | croak "Usage: memoize 'functionname'|coderef {OPTIONS}"; | ||||
54 | } | ||||
55 | |||||
56 | my $uppack = caller; # TCL me Elmo! | ||||
57 | my $cref; # Code reference to original function | ||||
58 | my $name = (ref $fn ? undef : $fn); | ||||
59 | |||||
60 | # Convert function names to code references | ||||
61 | 5 | 135µs | $cref = &_make_cref($fn, $uppack); # spent 135µs making 5 calls to Memoize::_make_cref, avg 27µs/call | ||
62 | |||||
63 | # Locate function prototype, if any | ||||
64 | my $proto = prototype $cref; | ||||
65 | if (defined $proto) { $proto = "($proto)" } | ||||
66 | else { $proto = "" } | ||||
67 | |||||
68 | # I would like to get rid of the eval, but there seems not to be any | ||||
69 | # other way to set the prototype properly. The switch here for | ||||
70 | # 'usethreads' works around a bug in threadperl having to do with | ||||
71 | # magic goto. It would be better to fix the bug and use the magic | ||||
72 | # goto version everywhere. | ||||
73 | 1 | 332µs | 5 | 95µs | my $wrapper = # spent 95µs making 5 calls to Config::FETCH, avg 19µs/call # spent 367µs executing statements in 5 string evals (merged) # includes 405µs spent executing 29 calls to 1 sub defined therein. |
74 | $Config{usethreads} | ||||
75 | ? eval "sub $proto { &_memoizer(\$cref, \@_); }" | ||||
76 | : eval "sub $proto { unshift \@_, \$cref; goto &_memoizer; }"; | ||||
77 | |||||
78 | my $normalizer = $options{NORMALIZER}; | ||||
79 | if (defined $normalizer && ! ref $normalizer) { | ||||
80 | $normalizer = _make_cref($normalizer, $uppack); | ||||
81 | } | ||||
82 | |||||
83 | my $install_name; | ||||
84 | if (defined $options->{INSTALL}) { | ||||
85 | # INSTALL => name | ||||
86 | $install_name = $options->{INSTALL}; | ||||
87 | } elsif (! exists $options->{INSTALL}) { | ||||
88 | # No INSTALL option provided; use original name if possible | ||||
89 | $install_name = $name; | ||||
90 | } else { | ||||
91 | # INSTALL => undef means don't install | ||||
92 | } | ||||
93 | |||||
94 | if (defined $install_name) { | ||||
95 | 5 | 10µs | $install_name = $uppack . '::' . $install_name # spent 10µs making 5 calls to Memoize::CORE:match, avg 2µs/call | ||
96 | unless $install_name =~ /::/; | ||||
97 | 3 | 769µs | 2 | 18µs | # spent 14µs (10+4) within Memoize::BEGIN@97 which was called:
# once (10µs+4µs) by Memoize::Memcached::BEGIN@8 at line 97 # spent 14µs making 1 call to Memoize::BEGIN@97
# spent 4µs making 1 call to strict::unimport |
98 | local($^W) = 0; # ``Subroutine $install_name redefined at ...'' | ||||
99 | *{$install_name} = $wrapper; # Install memoized version | ||||
100 | } | ||||
101 | |||||
102 | $revmemotable{$wrapper} = "" . $cref; # Turn code ref into hash key | ||||
103 | |||||
104 | # These will be the caches | ||||
105 | my %caches; | ||||
106 | for my $context (qw(SCALAR LIST)) { | ||||
107 | # suppress subsequent 'uninitialized value' warnings | ||||
108 | $options{"${context}_CACHE"} ||= ''; | ||||
109 | |||||
110 | my $cache_opt = $options{"${context}_CACHE"}; | ||||
111 | my @cache_opt_args; | ||||
112 | if (ref $cache_opt) { | ||||
113 | @cache_opt_args = @$cache_opt; | ||||
114 | $cache_opt = shift @cache_opt_args; | ||||
115 | } | ||||
116 | if ($cache_opt eq 'FAULT') { # no cache | ||||
117 | $caches{$context} = undef; | ||||
118 | } elsif ($cache_opt eq 'HASH') { # user-supplied hash | ||||
119 | my $cache = $cache_opt_args[0]; | ||||
120 | my $package = ref(tied %$cache); | ||||
121 | if ($context eq 'LIST' && $scalar_only{$package}) { | ||||
122 | croak("You can't use $package for LIST_CACHE because it can only store scalars"); | ||||
123 | } | ||||
124 | $caches{$context} = $cache; | ||||
125 | } elsif ($cache_opt eq '' || $IS_CACHE_TAG{$cache_opt}) { | ||||
126 | # default is that we make up an in-memory hash | ||||
127 | $caches{$context} = {}; | ||||
128 | # (this might get tied later, or MERGEd away) | ||||
129 | } else { | ||||
130 | croak "Unrecognized option to `${context}_CACHE': `$cache_opt' should be one of (@CONTEXT_TAGS); aborting"; | ||||
131 | } | ||||
132 | } | ||||
133 | |||||
134 | # Perhaps I should check here that you didn't supply *both* merge | ||||
135 | # options. But if you did, it does do something reasonable: They | ||||
136 | # both get merged to the same in-memory hash. | ||||
137 | if ($options{SCALAR_CACHE} eq 'MERGE') { | ||||
138 | $caches{SCALAR} = $caches{LIST}; | ||||
139 | } elsif ($options{LIST_CACHE} eq 'MERGE') { | ||||
140 | $caches{LIST} = $caches{SCALAR}; | ||||
141 | } | ||||
142 | |||||
143 | # Now deal with the TIE options | ||||
144 | { | ||||
145 | my $context; | ||||
146 | foreach $context (qw(SCALAR LIST)) { | ||||
147 | # If the relevant option wasn't `TIE', this call does nothing. | ||||
148 | 10 | 52µs | _my_tie($context, $caches{$context}, $options); # Croaks on failure # spent 52µs making 10 calls to Memoize::_my_tie, avg 5µs/call | ||
149 | } | ||||
150 | } | ||||
151 | |||||
152 | # We should put some more stuff in here eventually. | ||||
153 | # We've been saying that for serveral versions now. | ||||
154 | # And you know what? More stuff keeps going in! | ||||
155 | $memotable{$cref} = | ||||
156 | { | ||||
157 | O => $options, # Short keys here for things we need to access frequently | ||||
158 | N => $normalizer, | ||||
159 | U => $cref, | ||||
160 | MEMOIZED => $wrapper, | ||||
161 | PACKAGE => $uppack, | ||||
162 | NAME => $install_name, | ||||
163 | S => $caches{SCALAR}, | ||||
164 | L => $caches{LIST}, | ||||
165 | }; | ||||
166 | |||||
167 | $wrapper # Return just memoized version | ||||
168 | } | ||||
169 | |||||
170 | # This function tries to load a tied hash class and tie the hash to it. | ||||
171 | # spent 52µs within Memoize::_my_tie which was called 10 times, avg 5µs/call:
# 10 times (52µs+0s) by Memoize::memoize at line 148, avg 5µs/call | ||||
172 | 40 | 64µs | my ($context, $hash, $options) = @_; | ||
173 | my $fullopt = $options->{"${context}_CACHE"}; | ||||
174 | |||||
175 | # We already checked to make sure that this works. | ||||
176 | my $shortopt = (ref $fullopt) ? $fullopt->[0] : $fullopt; | ||||
177 | |||||
178 | return unless defined $shortopt && $shortopt eq 'TIE'; | ||||
179 | carp("TIE option to memoize() is deprecated; use HASH instead") | ||||
180 | if $^W; | ||||
181 | |||||
182 | my @args = ref $fullopt ? @$fullopt : (); | ||||
183 | shift @args; | ||||
184 | my $module = shift @args; | ||||
185 | if ($context eq 'LIST' && $scalar_only{$module}) { | ||||
186 | croak("You can't use $module for LIST_CACHE because it can only store scalars"); | ||||
187 | } | ||||
188 | my $modulefile = $module . '.pm'; | ||||
189 | $modulefile =~ s{::}{/}g; | ||||
190 | eval { require $modulefile }; | ||||
191 | if ($@) { | ||||
192 | croak "Memoize: Couldn't load hash tie module `$module': $@; aborting"; | ||||
193 | } | ||||
194 | my $rc = (tie %$hash => $module, @args); | ||||
195 | unless ($rc) { | ||||
196 | croak "Memoize: Couldn't tie hash to `$module': $!; aborting"; | ||||
197 | } | ||||
198 | 1; | ||||
199 | } | ||||
200 | |||||
201 | sub flush_cache { | ||||
202 | my $func = _make_cref($_[0], scalar caller); | ||||
203 | my $info = $memotable{$revmemotable{$func}}; | ||||
204 | die "$func not memoized" unless defined $info; | ||||
205 | for my $context (qw(S L)) { | ||||
206 | my $cache = $info->{$context}; | ||||
207 | if (tied %$cache && ! (tied %$cache)->can('CLEAR')) { | ||||
208 | my $funcname = defined($info->{NAME}) ? | ||||
209 | "function $info->{NAME}" : "anonymous function $func"; | ||||
210 | my $context = {S => 'scalar', L => 'list'}->{$context}; | ||||
211 | croak "Tied cache hash for $context-context $funcname does not support flushing"; | ||||
212 | } else { | ||||
213 | %$cache = (); | ||||
214 | } | ||||
215 | } | ||||
216 | } | ||||
217 | |||||
218 | # This is the function that manages the memo tables. | ||||
219 | # spent 1.61s (1.99ms+1.61) within Memoize::_memoizer which was called 29 times, avg 55.6ms/call:
# 29 times (1.99ms+1.61s) by Memoize::__ANON__[(eval 1015)[/usr/share/perl/5.10/Memoize.pm:73]:1] or Memoize::__ANON__[(eval 1026)[/usr/share/perl/5.10/Memoize.pm:73]:1] or Memoize::__ANON__[(eval 28)[/usr/share/perl/5.10/Memoize.pm:73]:1] or Memoize::__ANON__[(eval 30)[/usr/share/perl/5.10/Memoize.pm:73]:1] at line 1 of (eval 28)[Memoize.pm:73], avg 55.6ms/call | ||||
220 | 356 | 1.02ms | my $orig = shift; # stringized version of ref to original func. | ||
221 | my $info = $memotable{$orig}; | ||||
222 | my $normalizer = $info->{N}; | ||||
223 | |||||
224 | my $argstr; | ||||
225 | my $context = (wantarray() ? LIST : SCALAR); | ||||
226 | |||||
227 | if (defined $normalizer) { | ||||
228 | 3 | 400µs | 2 | 21µs | # spent 17µs (12+4) within Memoize::BEGIN@228 which was called:
# once (12µs+4µs) by Memoize::Memcached::BEGIN@8 at line 228 # spent 17µs making 1 call to Memoize::BEGIN@228
# spent 4µs making 1 call to strict::unimport |
229 | if ($context == SCALAR) { | ||||
230 | $argstr = &{$normalizer}(@_); | ||||
231 | } elsif ($context == LIST) { | ||||
232 | ($argstr) = &{$normalizer}(@_); | ||||
233 | } else { | ||||
234 | croak "Internal error \#41; context was neither LIST nor SCALAR\n"; | ||||
235 | } | ||||
236 | } else { # Default normalizer | ||||
237 | local $^W = 0; | ||||
238 | $argstr = join chr(28),@_; | ||||
239 | } | ||||
240 | |||||
241 | if ($context == SCALAR) { | ||||
242 | my $cache = $info->{S}; | ||||
243 | _crap_out($info->{NAME}, 'scalar') unless $cache; | ||||
244 | 1 | 887µs | 54 | 1.61s | if (exists $cache->{$argstr}) { # spent 815ms making 27 calls to Memoize::Memcached::EXISTS, avg 30.2ms/call
# spent 790ms making 27 calls to Memoize::Memcached::FETCH, avg 29.3ms/call |
245 | return $cache->{$argstr}; | ||||
246 | } else { | ||||
247 | 8 | 5.64ms | my $val = &{$info->{U}}(@_); # spent 5.58ms making 2 calls to C4::Koha::GetAuthorisedValues, avg 2.79ms/call
# spent 52µs making 4 calls to DBI::common::DESTROY, avg 13µs/call
# spent 6µs making 2 calls to DBD::_mem::common::DESTROY, avg 3µs/call | ||
248 | # Scalars are considered to be lists; store appropriately | ||||
249 | if ($info->{O}{SCALAR_CACHE} eq 'MERGE') { | ||||
250 | $cache->{$argstr} = [$val]; | ||||
251 | } else { | ||||
252 | $cache->{$argstr} = $val; | ||||
253 | } | ||||
254 | $val; | ||||
255 | } | ||||
256 | } elsif ($context == LIST) { | ||||
257 | my $cache = $info->{L}; | ||||
258 | _crap_out($info->{NAME}, 'list') unless $cache; | ||||
259 | if (exists $cache->{$argstr}) { | ||||
260 | my $val = $cache->{$argstr}; | ||||
261 | # If LISTCONTEXT=>MERGE, then the function never returns lists, | ||||
262 | # so we have a scalar value cached, so just return it straightaway: | ||||
263 | return ($val) if $info->{O}{LIST_CACHE} eq 'MERGE'; | ||||
264 | # Maybe in a later version we can use a faster test. | ||||
265 | |||||
266 | # Otherwise, we cached an array containing the returned list: | ||||
267 | return @$val; | ||||
268 | } else { | ||||
269 | my @q = &{$info->{U}}(@_); | ||||
270 | $cache->{$argstr} = $info->{O}{LIST_CACHE} eq 'MERGE' ? $q [0] : \@q; | ||||
271 | @q; | ||||
272 | } | ||||
273 | } else { | ||||
274 | croak "Internal error \#42; context was neither LIST nor SCALAR\n"; | ||||
275 | } | ||||
276 | } | ||||
277 | |||||
278 | sub unmemoize { | ||||
279 | my $f = shift; | ||||
280 | my $uppack = caller; | ||||
281 | my $cref = _make_cref($f, $uppack); | ||||
282 | |||||
283 | unless (exists $revmemotable{$cref}) { | ||||
284 | croak "Could not unmemoize function `$f', because it was not memoized to begin with"; | ||||
285 | } | ||||
286 | |||||
287 | my $tabent = $memotable{$revmemotable{$cref}}; | ||||
288 | unless (defined $tabent) { | ||||
289 | croak "Could not figure out how to unmemoize function `$f'"; | ||||
290 | } | ||||
291 | my $name = $tabent->{NAME}; | ||||
292 | if (defined $name) { | ||||
293 | 3 | 159µs | 2 | 23µs | # spent 18µs (13+5) within Memoize::BEGIN@293 which was called:
# once (13µs+5µs) by Memoize::Memcached::BEGIN@8 at line 293 # spent 18µs making 1 call to Memoize::BEGIN@293
# spent 5µs making 1 call to strict::unimport |
294 | local($^W) = 0; # ``Subroutine $install_name redefined at ...'' | ||||
295 | *{$name} = $tabent->{U}; # Replace with original function | ||||
296 | } | ||||
297 | undef $memotable{$revmemotable{$cref}}; | ||||
298 | undef $revmemotable{$cref}; | ||||
299 | |||||
300 | # This removes the last reference to the (possibly tied) memo tables | ||||
301 | # my ($old_function, $memotabs) = @{$tabent}{'U','S','L'}; | ||||
302 | # undef $tabent; | ||||
303 | |||||
304 | # # Untie the memo tables if they were tied. | ||||
305 | # my $i; | ||||
306 | # for $i (0,1) { | ||||
307 | # if (tied %{$memotabs->[$i]}) { | ||||
308 | # warn "Untying hash #$i\n"; | ||||
309 | # untie %{$memotabs->[$i]}; | ||||
310 | # } | ||||
311 | # } | ||||
312 | |||||
313 | $tabent->{U}; | ||||
314 | } | ||||
315 | |||||
316 | # spent 135µs (123+12) within Memoize::_make_cref which was called 5 times, avg 27µs/call:
# 5 times (123µs+12µs) by Memoize::memoize at line 61, avg 27µs/call | ||||
317 | 55 | 139µs | my $fn = shift; | ||
318 | my $uppack = shift; | ||||
319 | my $cref; | ||||
320 | my $name; | ||||
321 | |||||
322 | if (ref $fn eq 'CODE') { | ||||
323 | $cref = $fn; | ||||
324 | } elsif (! ref $fn) { | ||||
325 | 5 | 12µs | if ($fn =~ /::/) { # spent 12µs making 5 calls to Memoize::CORE:match, avg 2µs/call | ||
326 | $name = $fn; | ||||
327 | } else { | ||||
328 | $name = $uppack . '::' . $fn; | ||||
329 | } | ||||
330 | 3 | 308µs | 2 | 20µs | # spent 16µs (12+4) within Memoize::BEGIN@330 which was called:
# once (12µs+4µs) by Memoize::Memcached::BEGIN@8 at line 330 # spent 16µs making 1 call to Memoize::BEGIN@330
# spent 4µs making 1 call to strict::unimport |
331 | if (defined $name and !defined(&$name)) { | ||||
332 | croak "Cannot operate on nonexistent function `$fn'"; | ||||
333 | } | ||||
334 | # $cref = \&$name; | ||||
335 | $cref = *{$name}{CODE}; | ||||
336 | } else { | ||||
337 | my $parent = (caller(1))[3]; # Function that called _make_cref | ||||
338 | croak "Usage: argument 1 to `$parent' must be a function name or reference.\n"; | ||||
339 | } | ||||
340 | $DEBUG and warn "${name}($fn) => $cref in _make_cref\n"; | ||||
341 | $cref; | ||||
342 | } | ||||
343 | |||||
344 | sub _crap_out { | ||||
345 | my ($funcname, $context) = @_; | ||||
346 | if (defined $funcname) { | ||||
347 | croak "Function `$funcname' called in forbidden $context context; faulting"; | ||||
348 | } else { | ||||
349 | croak "Anonymous function called in forbidden $context context; faulting"; | ||||
350 | } | ||||
351 | } | ||||
352 | |||||
353 | 1 | 8µs | 1; | ||
354 | |||||
- - | |||||
359 | =head1 NAME | ||||
360 | |||||
- - | |||||
sub Memoize::CORE:match; # opcode |