Filename | /usr/share/perl/5.10/Memoize.pm |
Statements | Executed 129 statements in 2.30ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 191µs | 336µs | memoize | Memoize::
2 | 1 | 1 | 68µs | 5.45ms | _memoizer | Memoize::
1 | 1 | 1 | 23µs | 25µs | _make_cref | Memoize::
1 | 1 | 1 | 18µs | 64µs | BEGIN@27 | Memoize::
1 | 1 | 1 | 16µs | 34µs | BEGIN@30 | Memoize::
1 | 1 | 1 | 14µs | 18µs | BEGIN@228 | Memoize::
1 | 1 | 1 | 13µs | 17µs | BEGIN@293 | Memoize::
1 | 1 | 1 | 12µs | 18µs | BEGIN@97 | Memoize::
2 | 1 | 1 | 12µs | 12µs | _my_tie | Memoize::
1 | 1 | 1 | 12µs | 15µs | BEGIN@330 | Memoize::
1 | 1 | 1 | 11µs | 29µs | BEGIN@28 | Memoize::
1 | 1 | 1 | 10µs | 43µs | BEGIN@29 | Memoize::
1 | 1 | 1 | 9µs | 12µs | BEGIN@34 | Memoize::
2 | 2 | 1 | 4µs | 4µs | CORE:match (opcode) | 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 | 800ns | $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 | 30µs | 2 | 109µs | # spent 64µs (18+45) within Memoize::BEGIN@27 which was called:
# once (18µs+45µs) by C4::Koha::BEGIN@28 at line 27 # spent 64µs making 1 call to Memoize::BEGIN@27
# spent 45µs making 1 call to Exporter::import |
28 | 3 | 28µs | 2 | 47µs | # spent 29µs (11+18) within Memoize::BEGIN@28 which was called:
# once (11µs+18µs) by C4::Koha::BEGIN@28 at line 28 # spent 29µs making 1 call to Memoize::BEGIN@28
# spent 18µs making 1 call to Exporter::import |
29 | 3 | 35µs | 2 | 75µs | # spent 43µs (10+33) within Memoize::BEGIN@29 which was called:
# once (10µs+33µs) by C4::Koha::BEGIN@28 at line 29 # spent 43µs making 1 call to Memoize::BEGIN@29
# spent 33µs making 1 call to vars::import |
30 | 3 | 58µs | 2 | 52µs | # spent 34µs (16+18) within Memoize::BEGIN@30 which was called:
# once (16µs+18µs) by C4::Koha::BEGIN@28 at line 30 # spent 34µs making 1 call to Memoize::BEGIN@30
# spent 18µs making 1 call to Config::import |
31 | 1 | 30µs | @ISA = qw(Exporter); | ||
32 | 1 | 1µs | @EXPORT = qw(memoize); | ||
33 | 1 | 600ns | @EXPORT_OK = qw(unmemoize flush_cache); | ||
34 | 3 | 320µs | 2 | 16µs | # spent 12µs (9+3) within Memoize::BEGIN@34 which was called:
# once (9µs+3µs) by C4::Koha::BEGIN@28 at line 34 # spent 12µs making 1 call to Memoize::BEGIN@34
# spent 3µs making 1 call to strict::import |
35 | |||||
36 | 1 | 400ns | my %memotable; | ||
37 | 1 | 100ns | my %revmemotable; | ||
38 | 1 | 1µs | my @CONTEXT_TAGS = qw(MERGE TIE MEMORY FAULT HASH); | ||
39 | 1 | 5µ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 | 5µs | my %scalar_only = map {($_ => 1)} qw(DB_File GDBM_File SDBM_File ODBM_File NDBM_File); | ||
45 | |||||
46 | # spent 336µs (191+145) within Memoize::memoize which was called:
# once (191µs+145µs) by C4::Biblio::BEGIN@33 at line 81 of /usr/share/koha/lib/C4/Koha.pm | ||||
47 | 1 | 2µs | my $fn = shift; | ||
48 | 1 | 2µs | my %options = @_; | ||
49 | 1 | 1µs | my $options = \%options; | ||
50 | |||||
51 | 1 | 2µs | unless (defined($fn) && | ||
52 | (ref $fn eq 'CODE' || ref $fn eq '')) { | ||||
53 | croak "Usage: memoize 'functionname'|coderef {OPTIONS}"; | ||||
54 | } | ||||
55 | |||||
56 | 1 | 1µs | my $uppack = caller; # TCL me Elmo! | ||
57 | 1 | 300ns | my $cref; # Code reference to original function | ||
58 | 1 | 500ns | my $name = (ref $fn ? undef : $fn); | ||
59 | |||||
60 | # Convert function names to code references | ||||
61 | 1 | 3µs | 1 | 25µs | $cref = &_make_cref($fn, $uppack); # spent 25µs making 1 call to Memoize::_make_cref |
62 | |||||
63 | # Locate function prototype, if any | ||||
64 | 1 | 900ns | my $proto = prototype $cref; | ||
65 | 1 | 800ns | if (defined $proto) { $proto = "($proto)" } | ||
66 | 1 | 900ns | 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 | 90µs | 1 | 106µs | my $wrapper = # spent 106µs making 1 call to Config::FETCH # spent 25µs executing statements in string eval # includes 19µs spent executing 2 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 | 1 | 900ns | my $normalizer = $options{NORMALIZER}; | ||
79 | 1 | 800ns | if (defined $normalizer && ! ref $normalizer) { | ||
80 | $normalizer = _make_cref($normalizer, $uppack); | ||||
81 | } | ||||
82 | |||||
83 | 1 | 400ns | my $install_name; | ||
84 | 1 | 3µs | 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 | 1 | 1µs | if (defined $install_name) { | ||
95 | 1 | 10µs | 1 | 2µs | $install_name = $uppack . '::' . $install_name # spent 2µs making 1 call to Memoize::CORE:match |
96 | unless $install_name =~ /::/; | ||||
97 | 3 | 714µs | 2 | 24µs | # spent 18µs (12+6) within Memoize::BEGIN@97 which was called:
# once (12µs+6µs) by C4::Koha::BEGIN@28 at line 97 # spent 18µs making 1 call to Memoize::BEGIN@97
# spent 6µs making 1 call to strict::unimport |
98 | 1 | 4µs | local($^W) = 0; # ``Subroutine $install_name redefined at ...'' | ||
99 | 1 | 4µs | *{$install_name} = $wrapper; # Install memoized version | ||
100 | } | ||||
101 | |||||
102 | 1 | 3µs | $revmemotable{$wrapper} = "" . $cref; # Turn code ref into hash key | ||
103 | |||||
104 | # These will be the caches | ||||
105 | 1 | 500ns | my %caches; | ||
106 | 1 | 2µs | for my $context (qw(SCALAR LIST)) { | ||
107 | # suppress subsequent 'uninitialized value' warnings | ||||
108 | 2 | 4µs | $options{"${context}_CACHE"} ||= ''; | ||
109 | |||||
110 | 2 | 2µs | my $cache_opt = $options{"${context}_CACHE"}; | ||
111 | 2 | 600ns | my @cache_opt_args; | ||
112 | 2 | 700ns | if (ref $cache_opt) { | ||
113 | @cache_opt_args = @$cache_opt; | ||||
114 | $cache_opt = shift @cache_opt_args; | ||||
115 | } | ||||
116 | 2 | 6µs | 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 | 1 | 2µs | 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 | 2 | 1µs | my $context; | ||
146 | 1 | 1µs | foreach $context (qw(SCALAR LIST)) { | ||
147 | # If the relevant option wasn't `TIE', this call does nothing. | ||||
148 | 2 | 6µs | 2 | 12µs | _my_tie($context, $caches{$context}, $options); # Croaks on failure # spent 12µs making 2 calls to Memoize::_my_tie, avg 6µ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 | 1 | 8µs | $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 | 1 | 6µs | $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 12µs within Memoize::_my_tie which was called 2 times, avg 6µs/call:
# 2 times (12µs+0s) by Memoize::memoize at line 148, avg 6µs/call | ||||
172 | 2 | 2µs | my ($context, $hash, $options) = @_; | ||
173 | 2 | 2µs | my $fullopt = $options->{"${context}_CACHE"}; | ||
174 | |||||
175 | # We already checked to make sure that this works. | ||||
176 | 2 | 1µs | my $shortopt = (ref $fullopt) ? $fullopt->[0] : $fullopt; | ||
177 | |||||
178 | 2 | 9µs | 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 5.45ms (68µs+5.39) within Memoize::_memoizer which was called 2 times, avg 2.73ms/call:
# 2 times (68µs+5.39ms) by Memoize::__ANON__[(eval 998)[/usr/share/perl/5.10/Memoize.pm:73]:1] at line 1 of (eval 998)[Memoize.pm:73], avg 2.73ms/call | ||||
220 | 2 | 3µs | my $orig = shift; # stringized version of ref to original func. | ||
221 | 2 | 5µs | my $info = $memotable{$orig}; | ||
222 | 2 | 3µs | my $normalizer = $info->{N}; | ||
223 | |||||
224 | 2 | 500ns | my $argstr; | ||
225 | 2 | 2µs | my $context = (wantarray() ? LIST : SCALAR); | ||
226 | |||||
227 | 2 | 2µs | if (defined $normalizer) { | ||
228 | 3 | 358µs | 2 | 22µs | # spent 18µs (14+4) within Memoize::BEGIN@228 which was called:
# once (14µs+4µs) by C4::Koha::BEGIN@28 at line 228 # spent 18µ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 | 2 | 9µs | local $^W = 0; | ||
238 | 2 | 6µs | $argstr = join chr(28),@_; | ||
239 | } | ||||
240 | |||||
241 | 2 | 12µs | if ($context == SCALAR) { | ||
242 | 2 | 2µs | my $cache = $info->{S}; | ||
243 | 2 | 1µs | _crap_out($info->{NAME}, 'scalar') unless $cache; | ||
244 | 2 | 3µs | if (exists $cache->{$argstr}) { | ||
245 | return $cache->{$argstr}; | ||||
246 | } else { | ||||
247 | 2 | 9µs | 8 | 5.41ms | my $val = &{$info->{U}}(@_); # spent 5.39ms making 2 calls to C4::Koha::GetAuthorisedValues, avg 2.69ms/call
# spent 14µs making 4 calls to DBI::common::DESTROY, avg 4µs/call
# spent 5µs making 2 calls to DBD::_mem::common::DESTROY, avg 3µs/call |
248 | # Scalars are considered to be lists; store appropriately | ||||
249 | 2 | 7µs | if ($info->{O}{SCALAR_CACHE} eq 'MERGE') { | ||
250 | $cache->{$argstr} = [$val]; | ||||
251 | } else { | ||||
252 | 2 | 3µs | $cache->{$argstr} = $val; | ||
253 | } | ||||
254 | 2 | 900ns | $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 | 152µs | 2 | 21µs | # spent 17µs (13+4) within Memoize::BEGIN@293 which was called:
# once (13µs+4µs) by C4::Koha::BEGIN@28 at line 293 # spent 17µs making 1 call to Memoize::BEGIN@293
# spent 4µ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 25µs (23+2) within Memoize::_make_cref which was called:
# once (23µs+2µs) by Memoize::memoize at line 61 | ||||
317 | 1 | 600ns | my $fn = shift; | ||
318 | 1 | 500ns | my $uppack = shift; | ||
319 | 1 | 300ns | my $cref; | ||
320 | 1 | 400ns | my $name; | ||
321 | |||||
322 | 1 | 2µs | if (ref $fn eq 'CODE') { | ||
323 | $cref = $fn; | ||||
324 | } elsif (! ref $fn) { | ||||
325 | 1 | 11µs | 1 | 2µs | if ($fn =~ /::/) { # spent 2µs making 1 call to Memoize::CORE:match |
326 | $name = $fn; | ||||
327 | } else { | ||||
328 | 1 | 2µs | $name = $uppack . '::' . $fn; | ||
329 | } | ||||
330 | 3 | 268µs | 2 | 19µs | # spent 15µs (12+4) within Memoize::BEGIN@330 which was called:
# once (12µs+4µs) by C4::Koha::BEGIN@28 at line 330 # spent 15µs making 1 call to Memoize::BEGIN@330
# spent 4µs making 1 call to strict::unimport |
331 | 1 | 3µs | if (defined $name and !defined(&$name)) { | ||
332 | croak "Cannot operate on nonexistent function `$fn'"; | ||||
333 | } | ||||
334 | # $cref = \&$name; | ||||
335 | 1 | 2µs | $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 | 1 | 700ns | $DEBUG and warn "${name}($fn) => $cref in _make_cref\n"; | ||
341 | 1 | 4µs | $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 | 9µs | 1; | ||
354 | |||||
- - | |||||
359 | =head1 NAME | ||||
360 | |||||
- - | |||||
sub Memoize::CORE:match; # opcode |