File Coverage

blib/lib/Cache/Benchmark.pm
Criterion Covered Total %
statement 145 182 79.6
branch 55 82 67.0
condition 5 9 55.5
subroutine 12 15 80.0
pod 6 6 100.0
total 223 294 75.8


line stmt bran cond sub pod time code
1             package Cache::Benchmark;
2              
3 2     2   71095 use warnings;
  2         4  
  2         54  
4 2     2   9 use strict;
  2         4  
  2         56  
5              
6 2     2   2575 use Time::HiRes();
  2         6188  
  2         71  
7 2     2   20 use Carp();
  2         4  
  2         5567  
8              
9             my $KEY = 0;
10             my $PROB = 1;
11             my $STANDARD_VALUES = {
12             keys => 1_000,
13             min_key_length => 30,
14             access_counter => 100_000,
15             value => ('x' x 500),
16             test_type => 'weighted',
17             sleep_time => 0,
18             weighted_key_config => {
19             1.5 => 15,
20             10 => 10,
21             35 => 7,
22             50 => 5,
23             65 => 3,
24             85 => 2,
25             99 => 1,
26             },
27             };
28              
29             =head1 NAME
30              
31             Cache::Benchmark - Tests the quality and speed of a cache module to compare cachemodules and algorithms.
32              
33             =head1 VERSION
34              
35             Version 0.011
36              
37             =cut
38              
39             our $VERSION = '0.011';
40              
41              
42             =head1 SYNOPSIS
43              
44             use Cache::Benchmark();
45             use Cache::MemoryCache();
46             use Cache::SizeAwareMemoryCache();
47            
48             my $cache_1 = new Cache::MemoryCache({
49             namespace => 'my',
50             default_expires_in => 1,
51             });
52             my $cache_2 = new Cache::SizeAwareMemoryCache({
53             namespace => 'my',
54             default_expires_in => 1,
55             max_size => 400,
56             });
57            
58             my $test = new Cache::Benchmark();
59             $test->init( access_counter => 10_000 );
60            
61             $test->run($cache_1);
62             print $test->get_printable_result();
63            
64             $test->run($cache_2);
65             print $test->get_printable_result();
66              
67             =head1 EXPORT
68              
69             -
70              
71             =head1 CONSTRUCTOR
72              
73             =head2 new()
74              
75             =over 4
76              
77             No parameter. You have to L the object
78              
79             B __PACKAGE__
80              
81             B -
82              
83             =back
84              
85             =cut
86             sub new {
87 1     1 1 4075 my $package = $_[0];
88            
89 1   33     14 my $self = bless({}, ref($package) || $package);
90 1         8 $self->{'_keylist_length'} = 0;
91 1         3 $self->{'_access_counter'} = 0;
92 1         4 $self->{'_cache_value'} = '';
93 1         283 $self->{'_result'} = {};
94 1         6 $self->{'_is_init'} = 0;
95 1         4 $self->{'_test_type'} = '';
96 1         4 $self->{'_key_length'} = 0;
97 1         6 $self->{'_supported_types'} = [qw(plain random weighted)];
98 1         4 $self->{'_weighted_key_config'} = {};
99 1         4 $self->{'_accesslist'} = [];
100 1         3 $self->{'_sleep_time'} = 0;
101 1         4 return $self;
102             }
103              
104             =head1 METHODS
105              
106             =head2 init( [ L => INT, L => INT, L => INT, L => SCALAR, L => ENUM, L => HASHREF, L => FLOAT, L => ARRAYREF ] )
107              
108             =over 4
109              
110             Initialises and configures the benchmark-test. Without that, no other method will work. All parameters are optional.
111              
112             B BOOLEAN
113              
114             B
115              
116             =over 4
117              
118             =item B: INT [default: 1_000]
119              
120             how many cache keys are used
121              
122             =item B: INT [default: 30]
123              
124             the minimal length of any key in the cache. The standard-keys are integers (from 0 till defined "keys"),
125             if you define some min-length, the keys will be filled with initial zeros until reaching the given length.
126              
127             =item B: INT [default: 100_000]
128              
129             how many times will a cache key be get() or set() to the cache-module
130              
131             =item B: SCALAR [default: STRING, 500 bytes long]
132              
133             what the cache-value should be (can be anything except UNDEF, only to stress the memory usage)
134              
135             =item B: ENUM [default: weighted]
136              
137             types of test. These can be:
138              
139             =over 4
140              
141             =item C:
142              
143             not a real test. This will only call all keys one after another. No random, no peaks.
144              
145             =item C:
146              
147             only for access-speed tests. The key is randomly generated. No peaks.
148              
149             =item C:
150              
151             keys are randomly generated and weighted. Some keys have a
152             high chance of being used, others have less chances
153              
154             =back
155              
156             =item B: FLOAT [default: 0]
157              
158             the waiting time between each access in seconds. For example use 0.001 to wait a millisecond
159             between each access.
160              
161             =item B: [default: this example-config]
162              
163             an own config for the test_type "weighted". It's a simple hashref with the following structure:
164              
165             =over 4
166              
167             $config = {
168             1.5 => 15,
169             10 => 10,
170             35 => 7,
171             50 => 5,
172             65 => 3,
173             85 => 2,
174             99 => 1,
175             };
176              
177             =back
178              
179             I
180              
181             =over 4
182              
183             =item 1.5 => 15
184              
185             means: the first 1.5% of all keys have a 15 times higher chance to hit
186              
187             =item 10 => 10
188              
189             means: from 1.5% till 10% the keys will have a 10 times higher chance...
190              
191             =item 35 => 7
192              
193             means: from 10% till 35% ... 7 times higher ...
194             ...etc
195              
196             =back
197              
198             the key (percent) can be a FLOAT, value (weight) has to be an INT
199              
200             =item B: ARRAYREF [default: undef]
201              
202             sets the list of keys the benchmark-test will use in run(). (an ARRAYREF of INT) Usable to repeat exactly the same test
203             which was stored via L or to define your own list. If you give an access list, all other parameters,
204             except L, are senseless.
205              
206             Attention: the arrayref is not dereferenced!
207              
208             =back
209              
210             =back
211              
212             =cut
213             sub init {
214 5     5 1 131293 my $self = shift(@_);
215 5         28 my %config = @_;
216            
217 5         15 $self->{'_is_init'} = 0;
218            
219 5 100       29 my $keylist_length = exists($config{'keys'}) ? int(delete($config{'keys'})) : $STANDARD_VALUES->{'keys'};
220 5 100       28 my $key_length = exists($config{'min_key_length'}) ? int(delete($config{'min_key_length'})) : $STANDARD_VALUES->{'min_key_length'};
221 5 100       22 my $access_counter = exists($config{'access_counter'}) ? int(delete($config{'access_counter'})) : $STANDARD_VALUES->{'access_counter'};
222 5 50       26 my $cache_value = exists($config{'value'}) ? delete($config{'value'}) : $STANDARD_VALUES->{'value'};
223 5 100       785 my $test_type = exists($config{'test_type'}) ? delete($config{'test_type'}) : $STANDARD_VALUES->{'test_type'};
224 5 100       24 my $weighted_key_config = exists($config{'weighted_key_config'}) ? delete($config{'weighted_key_config'}) : $STANDARD_VALUES->{'weighted_key_config'};
225 5 50       16 my $sleep_time = exists($config{'sleep_time'}) ? delete($config{'sleep_time'}) : $STANDARD_VALUES->{'sleep_time'};
226 5 100       18 my $accesslist = exists($config{'accesslist'}) ? delete($config{'accesslist'}) : undef;
227            
228 5         21 foreach(keys %config) {
229 1         10 Carp::carp("init-parameter '$_' is unknown!");
230 1         12 return 0;
231             }
232 4 50       19 if($keylist_length < 10) {
233 0         0 Carp::carp("keylist length has to be bigger than 9");
234 0         0 return 0;
235             }
236 4 50       22 if($access_counter < 1) {
237 0         0 Carp::carp("access_counter has to be bigger than 0");
238 0         0 return 0;
239             }
240 4 50       17 if($access_counter <= $keylist_length) {
241 0         0 Carp::carp("for usable results the access_counter ($access_counter) has to be MUCH bigger than the keylist length ($keylist_length)");
242             }
243 4 50       12 if(!defined($cache_value)) {
244 0         0 Carp::carp("undefined cache-value is not allowed");
245 0         0 return 0;
246             }
247 4         8 my $type_ok = 0;
248 4         8 foreach my $type (@{$self->{'_supported_types'}}) {
  4         14  
249 12 100       97 $type_ok = 1 if($test_type eq $type);
250             }
251 4 50       14 if(!$type_ok) {
252 0         0 Carp::carp("test-type '$test_type' is not supported");
253 0         0 return 0;
254             }
255 4 50       17 if(ref($weighted_key_config) ne 'HASH') {
256 0         0 Carp::carp("weighted_key_config ($weighted_key_config) must be an hahsref");
257             }
258 4 50 66     43 if(defined($accesslist) && ref($accesslist) ne 'ARRAY') {
259 0         0 Carp::carp("parameter 'accesslist' has to be an arrayref of INT");
260 0         0 return 0;
261             }
262 4 50 66     21 if(defined($accesslist) && $#$accesslist == -1) {
263 0         0 Carp::carp("the 'accesslist' has no content");
264 0         0 return 0;
265             }
266 4         9 $self->{'_keylist_length'} = int($keylist_length);
267 4         9 $self->{'_access_counter'} = int($access_counter);
268 4         10 $self->{'_cache_value'} = $cache_value;
269 4         10 $self->{'_test_type'} = $test_type;
270 4 50       14 $self->{'_key_length'} = ($key_length > 0) ? int($key_length) : 0;
271 4         8 $self->{'_weighted_key_config'} = $weighted_key_config;
272 4 100       15 if(defined($accesslist)) {
273 1         2 $self->{'_accesslist'} = $accesslist;
274             } else {
275 3         19 $self->{'_accesslist'} = $self->_create_accesslist($self->{'_test_type'}, $self->{'_keylist_length'}, $self->{'_key_length'}, $self->{'_access_counter'}, $self->{'_weighted_key_config'});
276             }
277 4         5827 $self->{'_sleep_time'} = $sleep_time;
278              
279 4         13 $self->{'_is_init'} = 1;
280 4         65 return 1;
281             }
282              
283             =head2 run( L, [ L ] )
284              
285             =over 4
286              
287             Runs the benchmark-test with the given cache-object.
288              
289             B BOOLEAN
290              
291             B
292              
293             =over 4
294              
295             =item B: OBJECT
296              
297             every cache-object with an interface like the L Module. Only the following part of the interface is needed:
298              
299             =over 4
300              
301             =item set(key, value)
302              
303             sets a cache
304              
305             =item get(key)
306              
307             reads a cache
308              
309             =item purge()
310              
311             cleans up all overhanging caches (on sized cache modules)
312              
313             =back
314            
315             =item B: BOOLEAN [default: 0]
316              
317             should purge() called after any B or B? Useful for some SizeAware... Cache modules.
318              
319             =back
320              
321             =back
322              
323             =cut
324             sub run {
325 4     4 1 12 my $self = $_[0];
326 4         10 my $cache = $_[1];
327 4         8 my $auto_purge = $_[2];
328            
329 4 50       19 if(!$self->{'_is_init'}) {
330 0         0 Carp::carp('try to use uninitialised cache-test');
331 0         0 return 0;
332             }
333 4 50       17 return 0 if(!$self->_check_cache_class($cache));
334 4 100       33 $self->{'_result'} = $self->_run_benchmark($cache, $self->{'_accesslist'}, $self->{'_sleep_time'}, \$self->{'_cache_value'}, ($auto_purge ? 1 : 0), $self->{'_keylist_length'});
335 4         43 return 1;
336             }
337              
338             =head2 get_accesslist( )
339              
340             =over 4
341              
342             get the list of all accessed keys, which the benchmark-test will set() / get(). Usable to store this list and
343             repeat the test with exactly the same environment.
344              
345             Attention: the arrayref is not dereferenced!
346              
347             B ARRAYREF of INT
348              
349             B -
350              
351             =back
352              
353             =cut
354             sub get_accesslist {
355 0     0 1 0 my $self = $_[0];
356            
357 0 0       0 return [] if(!$self->{'_is_init'});
358 0         0 return $self->{'_accesslist'};
359             }
360              
361             =head2 get_raw_result( )
362              
363             =over 4
364              
365             returns all benchmark-data in a plain hash for further usage. Have a look at some L
366             to understand the data.
367              
368             B HASHREF
369              
370             B -
371              
372             =back
373              
374             =cut
375             sub get_raw_result {
376 0     0 1 0 my $self = $_[0];
377 0 0       0 if(!$self->{'_is_init'}) {
378 0         0 Carp::carp('try to use uninitialised object');
379 0         0 return {};
380             }
381 0         0 return $self->{'_result'};
382             }
383              
384             =head2 get_printable_result( )
385              
386             =over 4
387              
388             returns all benchmark-data as a readable string. Quality (cached access divided by uncached access) and runtime
389             (for all get() / set() / purge() operations) are the most important results to compare caches.
390              
391             B STRING
392              
393             B -
394              
395             =back
396              
397             =cut
398             sub get_printable_result {
399 0     0 1 0 my $self = $_[0];
400            
401 0 0       0 if(!$self->{'_is_init'}) {
402 0         0 Carp::carp('try to use uninitialised object');
403 0         0 return '';
404             }
405 0         0 return <
406             CONCLUSION FOR $self->{'_result'}->{'class'}:
407             --------------------------------------------------------------
408             Quality: $self->{'_result'}->{'quality'} (bigger is better)
409             Hint: $self->{'_result'}->{'quality_extra'}
410             Runtime: $self->{'_result'}->{'runtime'} s
411              
412             CONFIG:
413             -------
414             Accesses: $self->{'_result'}->{'access_counter'}
415             Keylist length: $self->{'_result'}->{'keylist_length'}
416             Sleep time: $self->{'_result'}->{'sleep_time'}s
417              
418             SINGLE VALUES:
419             --------------
420             Cache-keys read: $self->{'_result'}->{'reads'}
421             Cache-keys rewrite: $self->{'_result'}->{'rewrites'}
422             Cache-keys write: $self->{'_result'}->{'writes'}
423             Cache purged: $self->{'_result'}->{'purged'}
424              
425             Get-time: $self->{'_result'}->{'get_time'}
426             Set-time: $self->{'_result'}->{'set_time'}
427             Purge-time: $self->{'_result'}->{'purge_time'}
428             Runtime: $self->{'_result'}->{'runtime'}
429              
430             HERE
431             }
432              
433             # Protected: generates a random number from 0 to the given value
434             # int
435             sub _generate_random_number {
436 101000     101000   128566 my $self = $_[0];
437 101000         128910 my $max_val = $_[1];
438            
439 101000         338706 return sprintf("%.0f", rand(1) * $max_val);
440             }
441              
442             # Protected: fill a given key with 'x' till the min-length is reached
443             # string
444             sub _fill_key {
445 201009     201009   264446 my $self = $_[0];
446 201009         238234 my $key = $_[1];
447 201009         229222 my $min_length = $_[2];
448            
449 201009         281868 my $fill_length = $min_length - length($key);
450 201009 50       415311 return $key if($fill_length <= 0);
451 201009         716827 return ('0' x $fill_length) . $key;
452             }
453              
454             # Protected: generate all cache-keys for the bell-curve
455             # array( array( int, int ))
456             sub _create_accesslist {
457 3     3   7 my $self = $_[0];
458 3         4 my $test_type = $_[1];
459 3         6 my $keylist_length = $_[2];
460 3         5 my $key_length = $_[3];
461 3         4 my $access_counter = $_[4];
462 3         6 my $weighted_config = $_[5];
463            
464 3         7 my $list = [];
465 3 100       24 if($test_type eq 'plain') {
    100          
    50          
466 1         14 my $plain_list = [ 0..($keylist_length - 1) ];
467 1         5 my $i = 0;
468 1         3 foreach(1..$access_counter) {
469 1000 100       2560 $i = 0 if($i > $#$plain_list);
470 1000         2694 push(@$list, $self->_fill_key($plain_list->[$i++], $key_length));
471             }
472             } elsif($test_type eq 'random') {
473 1         5 foreach(1..$access_counter) {
474 100000         250915 push(@$list, $self->_fill_key($self->_generate_random_number($keylist_length - 1), $key_length) );
475             }
476             } elsif($test_type eq 'weighted') {
477 1         6 my @sorted_percents = sort({ $a <=> $b } keys(%$weighted_config));
  0         0  
478 1         3 my $actual_step = shift(@sorted_percents);
479 1         3 my $plain_keylist = [];
480 1         6 foreach my $key ( 0..($keylist_length - 1) ) {
481 10         15 my $weight = 1;
482 10 100       26 if(defined($actual_step)) {
483 2         10 my $percent = (($key + 1) / $keylist_length) * 100;
484 2 100       14 $actual_step = shift(@sorted_percents) if($actual_step < $percent);
485 2 100       11 $weight = int($weighted_config->{$actual_step}) if(defined($actual_step));
486             }
487 10         17 foreach(1..$weight) {
488 100009         208394 push(@$plain_keylist, $self->_fill_key($key, $key_length));
489             }
490             }
491 1         6 my $length = $#$plain_keylist;
492 1         3 foreach(1..$access_counter) {
493 1000         1786 push(@$list, $plain_keylist->[$self->_generate_random_number($length)]);
494             }
495             }
496 3         35 return $list;
497             }
498              
499             # Protected: check the object-interface of the given cache-object
500             # boolean
501             sub _check_cache_class {
502 4     4   9 my $self = $_[0];
503 4         7 my $cache = $_[1];
504            
505 4         12 foreach my $method (qw/set get purge/) {
506 12 50       75 if(!UNIVERSAL::can($cache, $method)) {
507 0         0 Carp::carp("You need to implement method $method in Class '" . ref($cache) . "'");
508 0         0 return 0;
509             }
510             }
511 4         16 return 1;
512             }
513              
514             # Protected: run the benchmark test
515             # hashref
516             sub _run_benchmark {
517 4     4   10 my $self = $_[0];
518 4         8 my $cache = $_[1];
519 4         7 my $access_list = $_[2];
520 4         7 my $sleep_time = $_[3];
521 4         9 my $cache_value = $_[4];
522 4         6 my $auto_purge = $_[5];
523 4         7 my $keylist_length = $_[6];
524              
525 4         9 my $cached_keys = {};
526 4         13 my ($cached, $not_cached, $cache_deleted, $cache_purged) = (0, 0, 0, 0);
527 4         8 my ($set_time, $get_time, $purge_time) = (0, 0, 0);
528 4         12 foreach my $key (@$access_list) {
529 102020 50       202855 if($sleep_time > 0) {
530 0         0 Time::HiRes::nanosleep($sleep_time);
531             }
532 102020 100       217372 if($cached_keys->{$key}) {
533 101899         214318 my $start_time = Time::HiRes::time();
534 101899         269280 my $val = $cache->get($key);
535 101899         735638 $get_time += Time::HiRes::time() - $start_time;
536 101899 50       182046 if(defined($val)) {
537 101899         157756 ++$cached;
538             } else {
539 0         0 ++$cache_deleted;
540 0         0 my $start_time = Time::HiRes::time();
541 0         0 $cache->set($key, $$cache_value);
542 0         0 $set_time += Time::HiRes::time() - $start_time;
543             }
544             } else {
545 121         123 ++$not_cached;
546 121         320 my $start_time = Time::HiRes::time();
547 121         3493 $cache->set($key, $$cache_value);
548 121         1131 $set_time += Time::HiRes::time() - $start_time;
549             }
550 102020         160134 $cached_keys->{$key} = 1;
551 102020         215768 my $start_time = Time::HiRes::time();
552 102020 100       275674 if($auto_purge) {
553 1020 50       2705 ++$cache_purged if($cache->purge());
554 1020         9402 $purge_time += Time::HiRes::time() - $start_time;
555             }
556             }
557 4         13 my $cache_written = $not_cached + $cache_deleted;
558 4 50       26 my $quality = $cache_deleted ? sprintf("%0.4f", $cached / $cache_deleted) : 9_999_999_999_999;
559             return {
560 4 50       253 class => ref($cache),
561             runtime => sprintf("%0.6f", $set_time + $get_time + $purge_time),
562             set_time => sprintf("%0.6f", $set_time),
563             get_time => sprintf("%0.6f", $get_time),
564             purge_time => sprintf("%0.6f", $purge_time),
565             keylist_length => $keylist_length,
566             quality => $quality,
567             quality_extra => ($cache_deleted ? '-' : 'no cachedata was cleared'),
568             access_counter => scalar(@$access_list),
569             reads => $cached,
570             rewrites => $cache_deleted,
571             writes => $not_cached,
572             purged => $cache_purged,
573             sleep_time => $sleep_time,
574              
575             };
576             }
577              
578             =head1 AUTHOR
579              
580             Tobias Tacke, C<< >>
581              
582             =head1 BUGS
583              
584             Please report any bugs or feature requests to
585             C, or through the web interface at
586             L.
587             I will be notified, and then you'll automatically be notified of any progress on
588             your bug as I make changes.
589              
590             =head1 SUPPORT
591              
592             You can find the documentation of this module with the perldoc command.
593              
594             perldoc Cache::Benchmark
595              
596             You can also look for information at:
597              
598             =over 4
599              
600             =item * AnnoCPAN: Annotated CPAN documentation
601              
602             L
603              
604             =item * CPAN Ratings
605              
606             L
607              
608             =item * RT: CPAN's request tracker
609              
610             L
611              
612             =item * Search CPAN
613              
614             L
615              
616             =back
617              
618             =head1 ACKNOWLEDGEMENTS
619              
620             =head1 COPYRIGHT & LICENSE
621              
622             Copyright 2007 Tobias Tacke, all rights reserved.
623              
624             This program is free software; you can redistribute it and/or modify it
625             under the same terms as Perl itself.
626              
627             =cut
628              
629             1; # End of Cache::Benchmark