File Coverage

blib/lib/Memoize/ExpireLRU.pm
Criterion Covered Total %
statement 132 171 77.1
branch 44 78 56.4
condition 12 17 70.5
subroutine 13 13 100.0
pod 0 2 0.0
total 201 281 71.5


line stmt bran cond sub pod time code
1             ###########################################################################
2             # File - ExpireLRU.pm
3             # Created 12 Feb, 2000, Brent B. Powers
4             #
5             # Purpose - This package implements LRU expiration. It does this by
6             # using a bunch of different data structures. Tuning
7             # support is included, but costs performance.
8             #
9             # ToDo - Test the further tie stuff
10             #
11             # Copyright(c) 2000 Brent B. Powers and B2Pi LLC
12             #
13             # You may copy and distribute this program under the same terms as
14             # Perl itself.
15             #
16             ###########################################################################
17             package Memoize::ExpireLRU;
18              
19 1     1   4682 use strict;
  1         2  
  1         36  
20 1     1   5141 use AutoLoader qw(AUTOLOAD);
  1         1613  
  1         5  
21 1     1   35 use Carp;
  1         1  
  1         63  
22 1     1   5 use vars qw($DEBUG $VERSION);
  1         1  
  1         67  
23              
24             $DEBUG = 0;
25             $VERSION = '0.55';
26              
27             # Usage: memoize func ,
28             # TIE => [
29             # Memoize::ExpireLRU,
30             # CACHESIZE => n,
31             # TUNECACHESIZE => m,
32             # INSTANCE => IDString
33             # TIE => [...]
34             # ]
35              
36             #############################################
37             ##
38             ## This used to all be a bit more reasonable, but then it turns out
39             ## that Memoize doesn't call FETCH if EXISTS returns true and it's in
40             ## scalar context. Thus, everything really has to be done in the
41             ## EXISTS code. Harumph.
42             ##
43             #############################################
44              
45 1     1   4 use vars qw(@AllTies $EndDebug);
  1         2  
  1         1571  
46              
47             $EndDebug = 0;
48              
49             1;
50              
51             sub TIEHASH {
52 3     3   5862 my ($package, %args, %cache, @index, @Tune, @Stats);
53 3         14 ($package, %args)= @_;
54 3         9 my($self) = bless \%args => $package;
55 3 50       13 $self->{CACHESIZE} or
56             croak "Memoize::ExpireLRU: CACHESIZE must be specified >0; aborting";
57 3   100     13 $self->{TUNECACHESIZE} ||= 0;
58 3 100       9 delete($self->{TUNECACHESIZE}) unless $self->{TUNECACHESIZE};
59 3         8 $self->{C} = \%cache;
60 3         5 $self->{I} = \@index;
61 3 50       8 defined($self->{INSTANCE}) or $self->{INSTANCE} = "$self";
62 3         7 foreach (@AllTies) {
63 3 50       14 if ($_->{INSTANCE} eq $self->{INSTANCE}) {
64 0         0 croak "Memoize::ExpireLRU: Attempt to register the same routine twice; aborting";
65             }
66             }
67 3 100       10 if ($self->{TUNECACHESIZE}) {
68 2         3 $EndDebug = 1;
69 2         6 for (my $i = 0; $i < $args{TUNECACHESIZE}; $i++) {
70 11         26 $Stats[$i] = 0;
71             }
72 2         5 $self->{T} = \@Stats;
73 2         4 $self->{TI} = \@Tune;
74 2         8 $self->{cm} = $args{ch} = $args{th} = 0;
75            
76             }
77              
78 3 50       8 if ($self->{TIE}) {
79 0         0 my($module, $modulefile, @opts, $rc, %tcache);
80 0         0 ($module, @opts) = @{$args{TIE}};
  0         0  
81 0         0 $modulefile = $module . '.pm';
82 0         0 $modulefile =~ s{::}{/}g;
83 0         0 eval { require $modulefile };
  0         0  
84 0 0       0 if ($@) {
85 0         0 croak "Memoize::ExpireLRU: Couldn't load hash tie module `$module': $@; aborting";
86             }
87 0         0 $rc = (tie %tcache => $module, @opts);
88 0 0       0 unless ($rc) {
89 0         0 croak "Memoize::ExpireLRU: Couldn't tie hash to `$module': $@; aborting";
90             }
91              
92             ## Preload our cache
93 0         0 foreach (keys %tcache) {
94 0         0 $self->{C}->{$_} = $tcache{$_}
95             }
96 0         0 $self->{TiC} = \%tcache;
97             }
98              
99 3         6 push(@AllTies, $self);
100 3         12 return $self;
101             }
102              
103             sub EXISTS {
104 46     46   2891 my($self, $key) = @_;
105              
106 46 50       92 $DEBUG and print STDERR " >> $self->{INSTANCE} >> EXISTS: $key\n";
107              
108 46 100       96 if (exists $self->{C}->{$key}) {
109 24         26 my($t, $i);#, %t, %r);
110              
111             ## Adjust the positions in the index cache
112             ## 1. Find the old entry in the array (and do the stat's)
113 24         105 $i = _find($self->{I}, $self->{C}->{$key}->{t}, $key);
114 24 50       50 if (!defined($i)) {
115 0         0 print STDERR "Cache trashed (unable to find $key)\n";
116 0         0 DumpCache($self->{INSTANCE});
117 0         0 ShowStats;
118 0         0 die "Aborting...";
119             }
120              
121             ## 2. Remove the old entry from the array
122 24         24 $t = splice(@{$self->{I}}, $i, 1);
  24         48  
123              
124             ## 3. Update the timestamp of the new array entry, as
125             ## well as that in the cache
126 24         53 $self->{C}->{$key}->{t} = $t->{t} = time;
127              
128             ## 4. Store the updated entry back into the array as the MRU
129 24         25 unshift(@{$self->{I}}, $t);
  24         39  
130              
131             ## 5. Adjust stats
132 24 100       54 if (defined($self->{T})) {
133 21 50       46 $self->{T}->[$i]++ if defined($self->{T});
134 21         23 $self->{ch}++;
135             }
136              
137 24 50       41 if ($DEBUG) {
138 0         0 print STDERR " Cache hit at $i";
139 0 0       0 print STDERR " ($self->{ch})" if defined($self->{T});
140 0         0 print STDERR ".\n";
141             }
142              
143 24         61 return 1;
144             } else {
145 22 100       41 if (exists($self->{TUNECACHESIZE})) {
146 16         22 $self->{cm}++;
147 16 50       31 $DEBUG and print STDERR " Cache miss ($self->{cm}).\n";
148             ## Ughhh. A linear search
149 16         16 my($i, $j);
150 16         30 for ($i = $j = $self->{CACHESIZE}; $i <= $#{$self->{T}}; $i++) {
  56         129  
151 45 100 66     333 next unless defined($self->{TI})
      66        
      100        
152             && defined($self->{TI}->[$i- $j])
153             && defined($self->{TI}->[$i - $j]->{k})
154             && $self->{TI}->[$i - $j]->{k} eq $key;
155 5         7 $self->{T}->[$i]++;
156 5         6 $self->{th}++;
157 5 50       10 $DEBUG and print STDERR " TestCache hit at $i. ($self->{th})\n";
158 5         6 splice(@{$self->{TI}}, $i - $j, 1);
  5         11  
159 5         16 return 0;
160             }
161             } else {
162 6 50       13 $DEBUG and print STDERR " Cache miss.\n";
163             }
164 17         46 return 0;
165             }
166             }
167              
168             sub STORE {
169 22     22   264 my ($self, $key, $value) = @_;
170 22 50       41 $DEBUG and print STDERR " >> $self->{INSTANCE} >> STORE: $key $value\n";
171              
172 22         21 my(%r, %t);
173 22         58 $t{t} = $r{t} = time;
174 22         29 $r{v} = $value;
175 22         36 $t{k} = $key;
176              
177             # Store the value into the hash
178 22         41 $self->{C}->{$key} = \%r;
179             ## As well as the tied cache, if it exists
180 22 50       53 $self->{TC}->{$key} = $value if defined($self->{TC});
181              
182             # By definition, this item is the MRU, so add it to the beginning
183             # of the LRU queue. Since this is a STORE, we know it doesn't already
184             # exist.
185 22         22 unshift(@{$self->{I}}, \%t);
  22         52  
186             ## Update the tied cache
187 22 50       48 $self->{TC}->{$key} = $value if defined($self->{TC});
188              
189             ## Do we have too many entries?
190 22         22 while (scalar(@{$self->{I}}) > $self->{CACHESIZE}) {
  35         89  
191             ## Chop off whatever is at the end
192             ## Get the key
193 13         14 $key = pop(@{$self->{I}});
  13         26  
194 13         35 delete($self->{C}->{$key->{k}});
195 13 50       28 delete($self->{TC}->{$key->{k}}) if defined($self->{TC});
196             ## Throw it to the beginning of the test cache
197 13 100       26 unshift(@{$self->{TI}}, $key) if defined($self->{T});
  11         40  
198             }
199              
200             ## Now, what about the Tuning Index
201 22 100       45 if (defined($self->{T})) {
202 16 100       17 if (scalar(@{$self->{TI}}) > $self->{TUNECACHESIZE} - $self->{CACHESIZE}) {
  16         45  
203 1         3 $#{$self->{TI}} = $self->{TUNECACHESIZE} - $self->{CACHESIZE} - 1;
  1         5  
204             }
205             }
206              
207 22         71 $value;
208             }
209              
210             sub FETCH {
211 24     24   140 my($self, $key) = @_;
212              
213 24 50       45 $DEBUG and print STDERR " >> $self->{INSTANCE} >> FETCH: $key\n";
214              
215 24         139 return $self->{C}->{$key}->{v};
216             }
217              
218             sub _find ( $$$ ) {
219 24     24   33 my($Aref, $time, $key) = @_;
220 24         21 my($t, $b, $n, $l);
221              
222 24         24 $t = $#{$Aref};
  24         34  
223 24         31 $n = $b = 0;
224 24         24 $l = -2;
225              
226 24         73 while ($time != $Aref->[$n]->{t}) {
227 0 0       0 if ($time < $Aref->[$n]->{t}) {
228 0         0 $b = $n;
229             } else {
230 0         0 $t = $n;
231             }
232 0 0       0 if ($t <= $b) {
233             ## Trouble, we're out.
234 0 0       0 if ($Aref->[$t]->{t} == $time) {
    0          
235 0         0 $n = $t;
236             } elsif ($Aref->[$b]->{t} == $time) {
237 0         0 $n = $b;
238             } else {
239             ## Really big trouble
240             ## Complain loudly
241 0         0 print "Trouble\n";
242 0         0 return undef;
243             }
244             } else {
245 0         0 $n = $b + (($t - $b) >> 1);
246 0 0       0 $n++ if $l == $n;
247 0         0 $l = $n;
248             }
249             }
250             ## Drop down in the array until the time isn't the time
251 24   33     53 while (($n > 0) && ($time == $Aref->[$n-1]->{t})) {
252 0         0 $n--;
253             }
254 24   66     114 while (($time == $Aref->[$n]->{t}) && ($key ne $Aref->[$n]->{k})) {
255 45         219 $n++;
256             }
257 24 50       49 if ($key ne $Aref->[$n]->{k}) {
258             ## More big trouble
259 0         0 print "More trouble\n";
260 0         0 return undef;
261             }
262 24         55 return $n;
263             }
264              
265             END {
266 1 50   1   8726 print STDERR ShowStats() if $EndDebug;
267             }
268              
269             __END__