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   76982 use strict;
  3         11  
  3         427  
4              
5 3     3   4076 use Carp::Assert;
  3         8031  
  3         24  
6 3     3   836 use base qw(Tie::Cache::LRU::Virtual);
  3         6  
  3         4059  
7              
8 3     3   45 use constant SUCCESS => 1;
  3         8  
  3         293  
9 3     3   18 use constant FAILURE => 0;
  3         6  
  3         150  
10              
11             # Node members.
12 3     3   13095 use enum qw(KEY VALUE PREV NEXT);
  3         6920  
  3         1152  
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   882 my($class, $max_size) = @_;
43 3         9 my $self = bless {}, $class;
44              
45 3 50       16 $max_size = $class->DEFAULT_MAX_SIZE unless defined $max_size;
46              
47 3         122 $self->_init;
48 3         40 $self->max_size($max_size);
49              
50 3         12 return $self;
51             }
52              
53              
54             sub _init {
55 4     4   9 my($self) = @_;
56              
57 4         37 $self->{size} = 0;
58 4         11 $self->{index} = {};
59 4         14 $self->{cache} = [];
60 4         20 $self->{low_idx} = -1;
61              
62 4         10 return SUCCESS;
63             }
64              
65              
66             sub FETCH {
67 6     6   683 my($self, $key) = @_;
68              
69 6 50       24 return unless exists $self->{index}{$key};
70              
71 6         20 $self->_promote($key);
72 6         26 return $self->{cache}[-1][VALUE];
73             }
74              
75              
76             sub _promote {
77 6     6   11 my($self, $key) = @_;
78 6         10 my $cache = $self->{cache};
79              
80 6         14 my $idx = $self->{index}{$key};
81 6         8 my $node = $cache->[$idx];
82              
83 6 100       8 return $node if $idx == $#{$cache};
  6         20  
84              
85 3         8 $cache->[$idx] = undef;
86 3         6 push @$cache, $node;
87 3         66 $self->{index}{$key} = $#{$cache};
  3         10  
88              
89 3 100       25 $self->_reorder_cache if $#$cache > $self->{size} * 2;
90 3         7 return $node;
91             }
92              
93              
94             sub _cull {
95 10     10   16 my($self) = @_;
96              
97 10         23 my $max_size = $self->max_size;
98 10         17 my $cache = $self->{cache};
99              
100 10 100       127 $self->_reorder_cache if $#$cache > $self->{size} * 2;
101              
102 10         19 my $idx = $self->{low_idx};
103 10         11 my $cache_size = $#{$cache};
  10         19  
104              
105 10         26 for( ; $self->{size} > $max_size; $self->{size}-- ) {
106 12         14 my $node;
107 12   66     15 do { $node = $cache->[++$idx]; }
  16         62  
108             until defined $node or $idx > $cache_size;
109              
110 12         24 delete $self->{index}{$node->[KEY]};
111 12         109 $cache->[$idx] = undef;
112             }
113              
114 10         17 $self->{low_idx} = $idx;
115              
116 10         19 return SUCCESS;
117             }
118              
119              
120             sub _reorder_cache {
121 2     2   8 my($self) = shift;
122 2         4 my $cache = $self->{cache};
123 2         5 my $next_spot = 0;
124              
125 2         5 foreach my $idx (0..$#{$cache}) {
  2         7  
126 18         27 my $node = $cache->[$idx];
127 18 100       60 next unless defined $node;
128 7 50       15 if( $idx == $next_spot ) {
129 0         0 $next_spot++;
130             }
131             else {
132 7         11 $cache->[$next_spot] = $node;
133 7         23 $self->{index}{$node->[KEY]} = $next_spot++;
134             }
135             }
136              
137 2         5 $#{$cache} = $next_spot - 1;
  2         10  
138 2         6 $self->{low_idx} = -1;
139             }
140              
141              
142             sub EXISTS {
143 2     2   446 my($self, $key) = @_;
144              
145 2         13 return exists $self->{index}{$key};
146             }
147              
148              
149             sub CLEAR {
150 1     1   3 my($self) = @_;
151 1         5 $self->_init;
152             }
153              
154              
155             sub STORE {
156 27     27   2515 my($self, $key, $val) = @_;
157              
158 27 50       161 if( exists $self->{index}{$key} ) {
159 0         0 my $node = $self->_promote($key);
160 0         0 $node->[VALUE] = $val;
161             }
162             else {
163 27         172 my $node = [];
164 27         39 @{$node}[KEY, VALUE] = ($key, $val);
  27         84  
165              
166 27         49 my $cache = $self->{cache};
167 27         45 push @$cache, $node;
168 27         30 $self->{index}{$key} = $#{$cache};
  27         73  
169 27         50 $self->{size}++;
170 27 100       105 $self->_cull if $self->{size} > $self->{max_size};
171             }
172 27         91 return SUCCESS;
173             }
174              
175              
176             sub DELETE {
177 5     5   1027 my($self, $key) = @_;
178              
179 5 50       23 return unless exists $self->{index}{$key};
180              
181 5         8 my $cache = $self->{cache};
182              
183 5         13 my $idx = delete $self->{index}{$key};
184 5         8 my $node = $cache->[$idx];
185 5         9 $cache->[$idx] = undef;
186              
187 5         8 $self->{size}--;
188              
189 5         26 return $node->[VALUE];
190             }
191              
192              
193             sub FIRSTKEY {
194 17     17   1653 my($self) = shift;
195              
196 17 100       57 return unless $self->{size};
197              
198 12         21 my $cache = $self->{cache};
199              
200 12         13 my @nodes;
201 12         24 for my $node (@$cache) {
202 84 100       187 push @nodes, $node if defined $node;
203             }
204              
205 12         29 $self->{nodes} = \@nodes;
206 12         134 $self->NEXTKEY;
207             }
208              
209              
210             sub NEXTKEY {
211 55     55   73 my $self = shift;
212              
213 55         79 my $node = pop @{$self->{nodes}};
  55         106  
214 55         227 return $node->[KEY];
215             }
216              
217              
218             sub max_size {
219 18     18 1 34 my($self) = shift;
220              
221 18 100       63 if(@_) {
222 5         10 my($new_max_size) = shift;
223 5   33     52 assert( defined $new_max_size && $new_max_size !~ /\D/ ) if DEBUG;
224 5         21 $self->{max_size} = $new_max_size;
225              
226 5 100       24 $self->_cull if $self->{size} > $new_max_size;
227              
228 5         11 return SUCCESS;
229             }
230             else {
231 13         49 return $self->{max_size};
232             }
233             }
234              
235              
236             sub curr_size {
237 6     6 1 594 my($self) = shift;
238              
239 6         21 assert(!@_) if DEBUG;
240              
241 6         38 return $self->{size};
242             }
243              
244              
245             sub DESTROY {
246 3     3   1230 my $self = shift;
247              
248             # Break a possible circular reference, just to be thorough.
249 3         139 $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;