File Coverage

blib/lib/Tie/Cache/LRU/Array.pm
Criterion Covered Total %
statement 123 126 97.6
branch 23 28 82.1
condition 3 6 50.0
subroutine 21 21 100.0
pod 2 2 100.0
total 172 183 93.9


line stmt bran cond sub pod time code
1             package Tie::Cache::LRU::Array;
2              
3 3     3   18715 use strict;
  3         5  
  3         117  
4              
5 3     3   1877 use Carp::Assert;
  3         4691  
  3         20  
6 3     3   500 use base qw(Tie::Cache::LRU::Virtual);
  3         4  
  3         1969  
7              
8 3     3   27 use constant SUCCESS => 1;
  3         6  
  3         247  
9 3     3   19 use constant FAILURE => 0;
  3         4  
  3         182  
10              
11             # Node members.
12 3     3   2200 use enum qw(KEY VALUE PREV NEXT);
  3         3801  
  3         26  
13              
14              
15             =pod
16              
17             =head1 NAME
18              
19             Tie::Cache::LRU::Array - Tie::Cache::LRU implemented using arrays
20              
21             =head1 SYNOPSIS
22              
23             use Tie::Cache::LRU::Array;
24              
25             tie %cache, 'Tie::Cache::LRU::Array', 500;
26              
27             ...the rest is as Tie::Cache::LRU...
28              
29             =head1 DESCRIPTION
30              
31             This is an alternative implementation of Tie::Cache::LRU using Perl
32             arrays and built-in array operations instead of a linked list. The
33             theory is that even though the algorithm employed is more expensive,
34             it will still be faster for small cache sizes (where small <= ??)
35             because the work is done inside perl (ie. higer big O, lower
36             constant). If nothing else, it should use less memory.
37              
38              
39             =cut
40              
41             sub TIEHASH {
42 3     3   672 my($class, $max_size) = @_;
43 3         9 my $self = bless {}, $class;
44              
45 3 50       12 $max_size = $class->DEFAULT_MAX_SIZE unless defined $max_size;
46              
47 3         9 $self->_init;
48 3         42 $self->max_size($max_size);
49              
50 3         11 return $self;
51             }
52              
53              
54             sub _init {
55 4     4   8 my($self) = @_;
56              
57 4         27 $self->{size} = 0;
58 4         8 $self->{index} = {};
59 4         11 $self->{cache} = [];
60 4         11 $self->{low_idx} = -1;
61              
62 4         8 return SUCCESS;
63             }
64              
65              
66             sub FETCH {
67 6     6   434 my($self, $key) = @_;
68              
69 6 50       19 return unless exists $self->{index}{$key};
70              
71 6         16 $self->_promote($key);
72 6         28 return $self->{cache}[-1][VALUE];
73             }
74              
75              
76             sub _promote {
77 6     6   7 my($self, $key) = @_;
78 6         7 my $cache = $self->{cache};
79              
80 6         11 my $idx = $self->{index}{$key};
81 6         8 my $node = $cache->[$idx];
82              
83 6 100       36 return $node if $idx == $#{$cache};
  6         22  
84              
85 3         7 $cache->[$idx] = undef;
86 3         5 push @$cache, $node;
87 3         4 $self->{index}{$key} = $#{$cache};
  3         8  
88              
89 3 100       24 $self->_reorder_cache if $#$cache > $self->{size} * 2;
90 3         6 return $node;
91             }
92              
93              
94             sub _cull {
95 10     10   7 my($self) = @_;
96              
97 10         13 my $max_size = $self->max_size;
98 10         11 my $cache = $self->{cache};
99              
100 10 100       22 $self->_reorder_cache if $#$cache > $self->{size} * 2;
101              
102 10         7 my $idx = $self->{low_idx};
103 10         10 my $cache_size = $#{$cache};
  10         11  
104              
105 10         14 for( ; $self->{size} > $max_size; $self->{size}-- ) {
106 12         7 my $node;
107 12   66     8 do { $node = $cache->[++$idx]; }
  16         38  
108             until defined $node or $idx > $cache_size;
109              
110 12         15 delete $self->{index}{$node->[KEY]};
111 12         23 $cache->[$idx] = undef;
112             }
113              
114 10         7 $self->{low_idx} = $idx;
115              
116 10         11 return SUCCESS;
117             }
118              
119              
120             sub _reorder_cache {
121 2     2   4 my($self) = shift;
122 2         3 my $cache = $self->{cache};
123 2         4 my $next_spot = 0;
124              
125 2         3 foreach my $idx (0..$#{$cache}) {
  2         7  
126 18         15 my $node = $cache->[$idx];
127 18 100       34 next unless defined $node;
128 7 50       10 if( $idx == $next_spot ) {
129 0         0 $next_spot++;
130             }
131             else {
132 7         9 $cache->[$next_spot] = $node;
133 7         14 $self->{index}{$node->[KEY]} = $next_spot++;
134             }
135             }
136              
137 2         4 $#{$cache} = $next_spot - 1;
  2         8  
138 2         13 $self->{low_idx} = -1;
139             }
140              
141              
142             sub EXISTS {
143 2     2   299 my($self, $key) = @_;
144              
145 2         7 return exists $self->{index}{$key};
146             }
147              
148              
149             sub CLEAR {
150 1     1   1 my($self) = @_;
151 1         2 $self->_init;
152             }
153              
154              
155             sub STORE {
156 27     27   887 my($self, $key, $val) = @_;
157              
158 27 50       55 if( exists $self->{index}{$key} ) {
159 0         0 my $node = $self->_promote($key);
160 0         0 $node->[VALUE] = $val;
161             }
162             else {
163 27         43 my $node = [];
164 27         25 @{$node}[KEY, VALUE] = ($key, $val);
  27         47  
165              
166 27         30 my $cache = $self->{cache};
167 27         26 push @$cache, $node;
168 27         18 $self->{index}{$key} = $#{$cache};
  27         76  
169 27         30 $self->{size}++;
170 27 100       62 $self->_cull if $self->{size} > $self->{max_size};
171             }
172 27         57 return SUCCESS;
173             }
174              
175              
176             sub DELETE {
177 5     5   414 my($self, $key) = @_;
178              
179 5 50       15 return unless exists $self->{index}{$key};
180              
181 5         8 my $cache = $self->{cache};
182              
183 5         8 my $idx = delete $self->{index}{$key};
184 5         8 my $node = $cache->[$idx];
185 5         6 $cache->[$idx] = undef;
186              
187 5         6 $self->{size}--;
188              
189 5         21 return $node->[VALUE];
190             }
191              
192              
193             sub FIRSTKEY {
194 17     17   1795 my($self) = shift;
195              
196 17 100       46 return unless $self->{size};
197              
198 12         12 my $cache = $self->{cache};
199              
200 12         12 my @nodes;
201 12         20 for my $node (@$cache) {
202 84 100       140 push @nodes, $node if defined $node;
203             }
204              
205 12         21 $self->{nodes} = \@nodes;
206 12         30 $self->NEXTKEY;
207             }
208              
209              
210             sub NEXTKEY {
211 55     55   61 my $self = shift;
212              
213 55         39 my $node = pop @{$self->{nodes}};
  55         55  
214 55         164 return $node->[KEY];
215             }
216              
217              
218             sub max_size {
219 18     18 1 24 my($self) = shift;
220              
221 18 100       29 if(@_) {
222 5         6 my($new_max_size) = shift;
223 5   33     36 assert( defined $new_max_size && $new_max_size !~ /\D/ ) if DEBUG;
224 5         22 $self->{max_size} = $new_max_size;
225              
226 5 100       18 $self->_cull if $self->{size} > $new_max_size;
227              
228 5         8 return SUCCESS;
229             }
230             else {
231 13         29 return $self->{max_size};
232             }
233             }
234              
235              
236             sub curr_size {
237 6     6 1 326 my($self) = shift;
238              
239 6         15 assert(!@_) if DEBUG;
240              
241 6         26 return $self->{size};
242             }
243              
244              
245             sub DESTROY {
246 3     3   712 my $self = shift;
247              
248             # Break a possible circular reference, just to be thorough.
249 3         36 $self->{nodes} = [];
250             }
251              
252              
253             =pod
254              
255             =head1 AUTHOR
256              
257             Michael G Schwern
258              
259             =head1 SEE ALSO
260              
261             L, L, L
262              
263             =cut
264              
265             1;