File Coverage

lib/Sub/Contract/Memoizer.pm
Criterion Covered Total %
statement 61 78 78.2
branch 12 30 40.0
condition 5 14 35.7
subroutine 13 17 76.4
pod 5 5 100.0
total 96 144 66.6


line stmt bran cond sub pod time code
1             #-------------------------------------------------------------------
2             #
3             # Sub::Contract::Memoizer - Implement the memoizing behaviour of a contract
4             #
5             # $Id: Memoizer.pm,v 1.12 2009/06/16 12:23:58 erwan_lemonnier Exp $
6             #
7              
8             package Sub::Contract::Memoizer;
9              
10 22     22   969 use strict;
  22         41  
  22         838  
11 22     22   124 use warnings;
  22         49  
  22         793  
12 22     22   129 use Carp qw(croak confess);
  22         53  
  22         1450  
13 22     22   115 use Data::Dumper;
  22         50  
  22         1093  
14 22     22   118 use Symbol;
  22         39  
  22         3277  
15 22     22   10152 use Sub::Contract::Cache;
  22         63  
  22         26784  
16              
17             our $VERSION = '0.12';
18              
19             #---------------------------------------------------------------
20             #
21             #
22             # the cache profiler
23             #
24             #
25             #---------------------------------------------------------------
26              
27             # turn on cache statistics
28             my $CACHE_STATS_ON = 0;
29             if (defined $ENV{PERL5SUBCONTRACTSTATS} && $ENV{PERL5SUBCONTRACTSTATS} eq '1') {
30             $CACHE_STATS_ON = 1;
31             }
32              
33             my %CACHE_STATS;
34              
35             # for the compiler to know if the cache profiler is on
36             sub _is_profiler_on {
37 32     32   167 return $CACHE_STATS_ON;
38             }
39              
40             # private functions used to update stat counters
41             sub _incr_miss {
42 27     27   475 $CACHE_STATS{$_[0]}->{calls}++;
43             }
44              
45             sub _incr_hit {
46 36     36   75 $CACHE_STATS{$_[0]}->{calls}++;
47 36         606 $CACHE_STATS{$_[0]}->{hits}++;
48             }
49              
50             sub _incr_max_reached {
51             # we don't want to increase this counter from within Sub::Contract::Cache->set for
52             # efficiency reasons, so we check after a set if the cache only contains 1 entry.
53             # if so, either it is the first entry (and then it happens only once and we count
54             # it away by setting maxsize to -1 at start) or the cache just got cleared
55              
56 27 100   27   387 if ($CACHE_STATS{$_[0]}->{cache}->{cache_size} == 1) {
57 8         162 $CACHE_STATS{$_[0]}->{maxsize}++;
58             }
59             }
60              
61             sub _get_cache_stats_report {
62 4     4   6 my $report = "";
63 4         7 my ($cntfnc,$cnthit,$cntqry,$cntclr) = (0,0,0,0);
64            
65             # find out max lentgh of functions fully qualified names
66 4         6 my $length = 0;
67 4         21 foreach my $func (sort keys %CACHE_STATS) {
68 28 100       56 $length = length $func if (length $func > $length);
69             }
70              
71 4         9 $report .= "------------------------------------------------------\n";
72 4         9 $report .= "Statistics from Sub::Contract's function result cache:\n";
73 4         5 $report .= "\n";
74            
75 4         17 foreach my $func (sort keys %CACHE_STATS) {
76 28         47 my $hits = $CACHE_STATS{$func}->{hits};
77 28         38 my $calls = $CACHE_STATS{$func}->{calls};
78 28         31 my $max = $CACHE_STATS{$func}->{maxsize};
79 28 50       49 $max = 0 if ($max == -1); # see _incr_max_reached for explanation
80 28         29 $cntfnc++;
81 28         27 $cnthit += $hits;
82 28         29 $cntqry += $calls;
83 28         27 $cntclr += $max;
84 28 50       45 if ($calls) {
85 28         50 my $rate = int(1000*$hits/$calls)/10;
86 28         180 $report .= " ".sprintf("%-".$length."s :",$func)." $rate % hits (calls: $calls, hits: $hits, max size reached: $max)\n";
87             }
88             }
89              
90 4         7 $report .= "\n";
91 4         9 $report .= " number of caches: $cntfnc\n";
92 4         7 $report .= " total calls: $cntqry\n";
93 4         7 $report .= " total hits: $cnthit\n";
94 4         9 $report .= " total max size reached: $cntclr\n";
95 4         5 $report .= "\n";
96 4         3 $report .= "------------------------------------------------------\n";
97              
98 4         69 return $report;
99             }
100              
101             # show cache statistics, if any
102             END {
103 22 100   22   15773 print _get_cache_stats_report if ($CACHE_STATS_ON);
104             }
105              
106             #---------------------------------------------------------------
107             #
108             #
109             # memoization - implement the cache behavior of a contract
110             #
111             #
112             #---------------------------------------------------------------
113              
114             sub cache {
115 8     8 1 664 my ($self,%args) = @_;
116 8   100     35 my $size = delete $args{size} || 10000; # default size 10000 elements
117              
118 8 50       21 croak "cache() got unknown arguments: ".Dumper(%args) if (%args);
119 8 50 33     54 croak "size should be a number" if (!defined $size || $size !~ /^\d+$/);
120              
121             # NOTE: $contract->reset() deletes this cache
122 8         27 $self->{cache} = new Sub::Contract::Cache( namespace => $self->contractor,
123             size => $size );
124              
125 8 100 66     33 if ($CACHE_STATS_ON && !exists $CACHE_STATS{$self->contractor}) {
126 7         51 $CACHE_STATS{$self->contractor} = { calls => 0, hits => 0, maxsize => -1, cache => $self->{cache} };
127             }
128              
129 8         56 return $self;
130             }
131              
132             sub get_cache {
133 0     0 1   return $_->{cache};
134             }
135              
136             sub has_cache {
137 0 0   0 1   return (exists $_->{cache}) ? 1:0;
138             }
139              
140             sub clear_cache {
141 0     0 1   my $self = shift;
142 0 0         confess "contract defines no cache" if (!exists $self->{cache});
143 0           $self->{cache}->clear;
144 0           return $self;
145             }
146              
147             sub add_to_cache {
148 0     0 1   my ($self,$args,$results) = @_;
149 0 0 0       confess "add_to_cache expects an array ref of arguments" if (!defined $args || ref $args ne "ARRAY");
150 0 0 0       confess "add_to_cache expects an array ref of results" if (!defined $results || ref $results ne "ARRAY");
151 0 0         confess "contract defines no cache" if (!exists $self->{cache});
152              
153             # we assume that the cached function is context insensitive and hence should
154             # return the same result independently of context.
155              
156             # the code to generate the key has to be the same as in Compiler.pm
157 0 0         my $key_array = join( ":", map( { (defined $_) ? $_ : "undef"; } "array", @$args) );
  0            
158 0 0         my $key_scalar = join( ":", map( { (defined $_) ? $_ : "undef"; } "scalar", @$args) );
  0            
159              
160 0           $self->{cache}->set($key_array,$results);
161 0           $self->{cache}->set($key_scalar,$results);
162              
163 0           return $self;
164             }
165              
166             1;
167              
168             =pod
169              
170             =head1 NAME
171              
172             Sub::Contract::Memoizer - Implement the caching behaviour of a contract
173              
174             =head1 SYNOPSIS
175              
176             See 'Sub::Contract'.
177              
178             =head1 DESCRIPTION
179              
180             Subroutine contracts defined with Sub::Contract can memoize
181             the contractor's results. This optional behaviour is implemented
182             in Sub::Contract::Memoizer.
183              
184             =head1 API
185              
186             See 'Sub::Contract'.
187              
188             =over 4
189              
190             =item C<< $contract->cache([size => $max_size]) >>
191              
192             =item C<< $contract->has_cache([size => $max_size]) >>
193              
194             =item C<< $contract->get_cache >>
195              
196             =item C<< $contract->clear_cache >>
197              
198             =item C<< $contract->add_to_cache(\@args, \@results) >>
199              
200             =back
201              
202             =head1 SEE ALSO
203              
204             See 'Sub::Contract'.
205              
206             =head1 VERSION
207              
208             $Id: Memoizer.pm,v 1.12 2009/06/16 12:23:58 erwan_lemonnier Exp $
209              
210             =head1 AUTHOR
211              
212             Erwan Lemonnier C<< >>
213              
214             =head1 LICENSE
215              
216             See Sub::Contract.
217              
218             =cut
219              
220              
221