root/trunk/lib/Perlbal/Cache.pm

Revision 549, 6.3 kB (checked in by hachi, 3 years ago)

r18488@lj: lj | 2006-08-29 16:07:02 -0700
Let's add a cache size statistic output

Line 
1# (This is a copy of Cache::SimpleLRU.)
2# License to use and redistribute this under the same terms as Perl itself.
3
4package Perlbal::Cache;
5
6use strict;
7use fields qw(items size tail head maxsize);
8
9use vars qw($VERSION);
10use constant PREVREF => 0;  # ptr left, to newer item
11use constant VALUE   => 1;
12use constant NEXTREF => 2;  # ptr right, to older item
13use constant KEY     => 3;  # copy of key for unlinking from namespace on fallout
14
15$VERSION = '1.0';
16
17sub new {
18    my $class = shift;
19    my $self = fields::new($class);
20    my $args = @_ == 1 ? $_[0] : { @_ };
21
22    $self->{head}    = undef,
23    $self->{tail}    = undef,
24    $self->{items}   = {}; # key -> arrayref, indexed by constants above
25    $self->{size}    = 0;
26    $self->{maxsize} = $args->{maxsize}+0;
27    return $self;
28}
29
30# need to DESTROY to cleanup doubly-linked list (circular refs)
31sub DESTROY {
32    my $self = shift;
33    $self->set_maxsize(0);
34    $self->validate_list;
35}
36
37# calls $code->($val) for each value in cache.  $code must return true
38# to continue walking.  foreach returns true if you hit the end.
39sub foreach {
40    my Perlbal::Cache $self = shift;
41    my $code = shift;
42    my $iter = $self->{head};
43    while ($iter) {
44        my $val = $iter->[VALUE];
45        $iter = $iter->[NEXTREF];
46        last unless $code->($val);
47    }
48    return $iter ? 0 : 1;
49}
50
51sub size {
52    my Perlbal::Cache $self = shift;
53    return $self->{size};
54}
55
56sub maxsize {
57    my Perlbal::Cache $self = shift;
58    return $self->{maxsize};
59}
60
61sub set_maxsize {
62    my ($self, $maxsize) = @_;
63    $self->{maxsize} = $maxsize;
64    $self->drop_tail while
65        $self->{size} > $self->{maxsize};
66}
67
68# For debugging only
69sub validate_list {
70    my ($self) = @_;
71
72    die "no tail pointer\n" if $self->{size} && ! $self->{tail};
73    die "no head pointer\n" if $self->{size} && ! $self->{head};
74    die "unwanted tail pointer\n" if ! $self->{size} && $self->{tail};
75    die "unwanted head pointer\n" if ! $self->{size} && $self->{head};
76
77    my $iter = $self->{head};
78    my $last = undef;
79    my $count = 1;
80    while ($count <= $self->{size}) {
81        if (! defined $iter) {
82            die "undefined iterator on element \#$count (trying to get to size $self->{size})\n";
83        }
84        my $key = $iter->[KEY];
85        my $it_via_hash = $self->{items}->{$key} or
86            die "item '$key' found in list, but not in hash\n";
87
88        unless ($it_via_hash == $iter) {
89            die "Hash value of '$key' maps to different node than we found.\n";
90        }
91
92        if ($count == 1 && $iter->[PREVREF]) {
93            die "Head element shouldn't have previous pointer!\n";
94        }
95        if ($count == $self->{size} && $iter->[NEXTREF]) {
96            die "Last element shouldn't have next pointer!\n";
97        }
98        if ($iter->[NEXTREF] && $iter->[NEXTREF]->[PREVREF] != $iter) {
99            die "next's previous should be us.\n";
100        }
101        if ($last && $iter->[PREVREF] != $last) {
102            die "defined \$last but its previous isn't us.\n";
103        }
104        if ($last && $last->[NEXTREF] != $iter) {
105            die "defined \$last but our next isn't it\n";
106        }
107        if (!$last && $iter->[PREVREF]) {
108            die "uh, we have a nextref but shouldn't\n";
109        }
110
111        $last = $iter;
112        $iter = $iter->[NEXTREF];
113        $count++;
114    }
115    return 1;
116}
117
118sub drop_tail {
119    my Perlbal::Cache $self = shift;
120    die "no tail (size)" unless $self->{size};
121
122    ## who's going to die?
123    my $to_die = $self->{tail} or die "no tail (key)";
124
125    ## set the tail to the item before the one dying.
126    $self->{tail} = $self->{tail}->[PREVREF];
127
128    ## adjust the forward pointer on the tail to be undef
129    if (defined $self->{tail}) {
130        $self->{tail}->[NEXTREF] = undef;
131    }
132
133    ## kill the item
134    delete $self->{items}->{$to_die->[KEY]};
135
136    ## shrink the overall size
137    $self->{size}--;
138
139    if (!$self->{size}) {
140        $self->{head} = undef;
141    }
142}
143
144sub get {
145    my Perlbal::Cache $self = shift;
146    my ($key) = @_;
147
148    my $item = $self->{items}{$key} or
149        return undef;
150
151    # promote this to the head
152    unless ($self->{head} == $item) {
153        if ($self->{tail} == $item) {
154            $self->{tail} = $item->[PREVREF];
155        }
156
157        # remove this element from the linked list.
158        my $next = $item->[NEXTREF];
159        my $prev = $item->[PREVREF];
160        if ($next) { $next->[PREVREF] = $prev; }
161        if ($prev) { $prev->[NEXTREF] = $next; }
162
163        # make current head point backwards to this item
164        $self->{head}->[PREVREF] = $item;
165
166        # make this item point forwards to current head, and backwards nowhere
167        $item->[NEXTREF] = $self->{head};
168        $item->[PREVREF] = undef;
169
170        # make this the new head
171        $self->{head} = $item;
172    }
173
174    return $item->[VALUE];
175}
176
177sub remove {
178    my Perlbal::Cache $self = shift;
179    my ($key) = @_;
180
181    my $item = $self->{items}{$key} or
182        return 0;
183    delete $self->{items}{$key};
184    $self->{size}--;
185
186    if (!$self->{size}) {
187        $self->{head} = undef;
188        $self->{tail} = undef;
189        return 1;
190    }
191
192    if ($self->{head} == $item) {
193        $self->{head} = $item->[NEXTREF];
194        $self->{head}->[PREVREF] = undef;
195        return 1;
196    }
197    if ($self->{tail} == $item) {
198        $self->{tail} = $item->[PREVREF];
199        $self->{tail}->[NEXTREF] = undef;
200        return 1;
201    }
202
203    # remove from middle
204    $item->[PREVREF]->[NEXTREF] = $item->[NEXTREF];
205    $item->[NEXTREF]->[PREVREF] = $item->[PREVREF];
206    return 1;
207
208}
209
210sub set {
211    my Perlbal::Cache $self = shift;
212    my ($key, $value) = @_;
213
214    $self->drop_tail while
215        $self->{maxsize} &&
216        $self->{size} >= $self->{maxsize} &&
217        ! exists $self->{items}->{$key};
218
219    if (exists $self->{items}->{$key}) {
220        # update the value
221        my $it = $self->{items}->{$key};
222        $it->[VALUE] = $value;
223    } else {
224        # stick it at the end, for now
225        my $it = $self->{items}->{$key} = [];
226        $it->[PREVREF] = undef;
227        $it->[NEXTREF] = undef;
228        $it->[KEY]     = $key;
229        $it->[VALUE] = $value;
230        if ($self->{size}) {
231            $self->{tail}->[NEXTREF] = $it;
232            $it->[PREVREF] = $self->{tail};
233        } else {
234            $self->{head} = $it;
235        }
236        $self->{tail} = $it;
237        $self->{size}++;
238    }
239
240    # this will promote it to the top:
241    $self->get($key);
242}
243
2441;
Note: See TracBrowser for help on using the browser.