cache_lib 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 49951bebe666d8d905c8cb74e5b1324f95f460da
4
+ data.tar.gz: eb58345e35655ea65121a69d22f6d936c9eae506
5
+ SHA512:
6
+ metadata.gz: 51a1d55a39dbacf762ab5b1b58275de0018fb968d7b200e7dbddbed4f159e1f3691ebd96232999a484fcffc355f9703922f3bc3f43017dd66076a0ce5c48bd28
7
+ data.tar.gz: f38f39b35db8a674c052184ba4ee32e8b7c640d06f56574ef5d528f9c4e1356ae38610949b4329d55cf48cc370b00e5007296aeab4826f3df09a1d4ba6a45607
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015 Kaijah Hougham
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # CacheLib
2
+ #### A Ruby caching library implementing Basic, FIFO, LRU and LIRS caches.
3
+
4
+ ##### CacheLib is young library and breaking api changes are still possible. Please read the release notes before upgrading.
5
+ CacheLib currently implements basic, FIFO, LRU and LIRS caches along with offering thread safe implementations of each. Originally a LIRS cache feature fork of [LruRedux](https://github.com/SamSaffron/lru_redux), CacheLib was built to provide a clean base for implementing the LIRS cache in Ruby, along with offering user friendly api.
6
+
7
+ ##### Basic:
8
+ The basic cache is the simplest cache available and forms the base for the others. It does NOT have an eviction strategy and will store however much it is given.
9
+
10
+ ##### FIFO:
11
+ The FIFO cache adds to the basic cache with a simple First In First Our eviction strategy to limit the cache size to a user set limit.
12
+
13
+ ##### LRU:
14
+ The LRU cache further adds to the previous caches with a Least Recently Used eviction strategy. The primary difference between FIFO and LRU is that LRU will refresh currently cached items if they are requested again.
15
+
16
+ ##### LIRS:
17
+ The LIRS cache implements the Low Inter-Reference Recency Set Replacement Policy developed by Song Jiang and Xiaodong Zhang. Please reference the [original paper](http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.116.2184) for more information.
18
+
19
+ ##### CacheLib is Ruby >= 1.9.3 only.
20
+ If you are looking for a LRU cache that supports an earlier version or just want an alternative LRU cache, please take a look at [LruRedux](https://github.com/SamSaffron/lru_redux).
21
+
22
+ ## Usage
23
+ ### Creating a cache
24
+ ```ruby
25
+ require 'cache_lib'
26
+
27
+ # Basic
28
+ cache = CacheLib.create :basic
29
+
30
+ # FIFO with a limit of 100
31
+ cache = CacheLib.create :fifo, 100
32
+
33
+ # LRU with a limit of 100
34
+ cache = CacheLib.create :lru, 100
35
+
36
+ # LIRS with a cache limit of 100, made up of a S limit of 95 and a Q limit of 5.
37
+ cache = CacheLib.create :lirs, 95, 5
38
+ ```
39
+
40
+ ### Using the cache
41
+ ```ruby
42
+ # Cache methods remain the same across all variations
43
+ cache = CacheLib.create :lru, 100
44
+
45
+ # .get is the primary method for accessing the cache. It requires a key and block that will generate the value that is stored if the key is not cached.
46
+ cache.get(:a) { 'Apple' }
47
+ # => 'Apple'
48
+ cache.get(:b) { 'Beethoven' }
49
+ # => 'Beethoven'
50
+ cache.get(:a) { 'Apricot' }
51
+ # => 'Apple'
52
+
53
+ # .store takes a key and value and implements the functionality of Hash#store. Will refresh the key in LRU and LIRS caches.
54
+ cache.store(:c, 'Chopin')
55
+ # => 'Chopin'
56
+ cache.store(:a, 'Mozart')
57
+ # => 'Mozart'
58
+
59
+ # []= is equivalent to .store.
60
+ cache[:d] = 'Denmark'
61
+ # => 'Denmark'
62
+
63
+ # .lookup takes a key and returns either the value if the key is cached or nil if it is not.
64
+ cache.lookup(:c)
65
+ # => 'Chopin'
66
+ cache.lookup(:t)
67
+ # => nil
68
+
69
+ # [] is equivalent to .lookup.
70
+ cache[:a]
71
+ # => 'Mozart'
72
+
73
+ # .fetch is similar to .lookup, but takes an optional block that will execute if the key is not cached.
74
+ cache.fetch(:c)
75
+ # => 'Chopin'
76
+ cache.fetch(:r) { 21 * 2 }
77
+ # => 42
78
+
79
+ # .evict takes a key and will remove the key from the cache and return the associated value. Returns nil is the key is not cached.
80
+ cache.evict(:d)
81
+ # => 'Denmark'
82
+ cache.evict(:r)
83
+ # => nil
84
+
85
+ # .each takes each key value pair and applies the given block to them.
86
+ cache.each { |_, value| puts value }
87
+ # => 'Chopin'
88
+ # => 'Mozart'
89
+ # => 'Beethoven'
90
+
91
+ # .key? takes a key and returns true if it is cached and false if it is not.
92
+ cache.key?(:a)
93
+ # => true
94
+ cache.key?(:g)
95
+ # => false
96
+
97
+ # .to_a returns an array of arrays of key value pairs that are cached. Basic, FIFO and LIRS are insertion ordered while LRU is recency ordered, starting with newest.
98
+ cache.to_a
99
+ # => [[:c, 'Chopin'], [:a, 'Mozart'], [:b, 'Beethoven']]
100
+
101
+ # .keys returns an array of the keys that are cached.
102
+ cache.keys
103
+ # => [:c, :a, :b]
104
+
105
+ # .values returns an array of the values that are cached.
106
+ cache.values
107
+ # => ['Chopin', 'Mozart', 'Beethoven']
108
+
109
+ # .size returns the current number of items that are cached.
110
+ cache.size
111
+ # => 3
112
+
113
+ # .clear removes all items from the cache.
114
+ cache.clear
115
+ # => nil
116
+ ```
117
+
118
+ ## Copyright
119
+
120
+ Copyright (c) 2015 Kaijah Hougham
121
+
122
+ See [LICENSE.txt](LICENSE.txt) for details.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/cache_lib/test*.rb']
7
+ t.verbose = true
8
+ end
data/cache_lib.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/cache_lib/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'cache_lib'
7
+ gem.version = CacheLib::VERSION
8
+ gem.summary = 'A Ruby Caching Library'
9
+ gem.description = 'A Ruby caching library implementing Basic, FIFO, LRU and LIRS caching strategies.'
10
+ gem.license = 'MIT'
11
+ gem.authors = 'Kaijah Hougham'
12
+ gem.email = 'github@seberius.com'
13
+ gem.homepage = 'https://github.com/seberius/cache_lib'
14
+
15
+ gem.required_ruby_version = '>= 1.9.3'
16
+
17
+ gem.files = `git ls-files`.split($RS)
18
+ gem.executables = gem.files.grep(/bin/).map { |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(/test/)
20
+ gem.require_paths = ['lib']
21
+
22
+ gem.add_development_dependency 'bundler', '~> 1.7'
23
+ gem.add_development_dependency 'rake', '~> 10'
24
+ gem.add_development_dependency 'minitest', '~> 5.0'
25
+ end
@@ -0,0 +1,109 @@
1
+ module CacheLib
2
+ class BasicCache
3
+ attr_reader :limit
4
+
5
+ def initialize
6
+ @limit = nil
7
+
8
+ @cache = UtilHash.new
9
+ end
10
+
11
+ def initialize_copy(source)
12
+ source_raw = source.raw
13
+
14
+ @limit = source_raw[:limit]
15
+
16
+ @cache = source_raw[:cache]
17
+ end
18
+
19
+ def limit=(_)
20
+ @limit = nil
21
+ end
22
+
23
+ def get(key)
24
+ value = hit(key)
25
+
26
+ if value
27
+ value
28
+ else
29
+ miss(key, yield)
30
+ end
31
+ end
32
+
33
+ def store(key, value)
34
+ miss(key, value)
35
+ end
36
+
37
+ def lookup(key)
38
+ @cache[key]
39
+ end
40
+
41
+ def fetch(key)
42
+ has_key = true
43
+ value = @cache.fetch(key) { has_key = false }
44
+
45
+ if has_key
46
+ value
47
+ else
48
+ yield if block_given?
49
+ end
50
+ end
51
+
52
+ def evict(key)
53
+ @cache.delete(key)
54
+ end
55
+
56
+ def clear
57
+ @cache.clear
58
+ nil
59
+ end
60
+
61
+ def each
62
+ @cache.each do |pair|
63
+ yield pair
64
+ end
65
+ end
66
+
67
+ def key?(key)
68
+ @cache.key?(key)
69
+ end
70
+
71
+ def to_a
72
+ @cache.to_a.reverse!
73
+ end
74
+
75
+ def keys
76
+ @cache.keys.reverse!
77
+ end
78
+
79
+ def values
80
+ @cache.values.reverse!
81
+ end
82
+
83
+ def size
84
+ @cache.size
85
+ end
86
+
87
+ def raw
88
+ { limit: @limit,
89
+ cache: @cache.clone }
90
+ end
91
+
92
+ def inspect
93
+ "#{self.class} currently caching #{@cache.size} items."
94
+ end
95
+
96
+ alias_method :[], :lookup
97
+ alias_method :[]=, :store
98
+
99
+ protected
100
+
101
+ def hit(key)
102
+ @cache[key]
103
+ end
104
+
105
+ def miss(key, value)
106
+ @cache[key] = value
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,46 @@
1
+ module CacheLib
2
+ class FifoCache < BasicCache
3
+ def initialize(*args)
4
+ limit, _ = args
5
+
6
+ fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
7
+ limit.nil? || limit < 1
8
+
9
+ @limit = limit
10
+
11
+ @cache = UtilHash.new
12
+ end
13
+
14
+ def limit=(args)
15
+ limit, _ = args
16
+ fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
17
+ limit.nil? || limit < 1
18
+
19
+ @limit = limit
20
+
21
+ resize
22
+ end
23
+
24
+ def inspect
25
+ "#{self.class} with a limit of #{@limit} "\
26
+ "currently caching #{@cache.size} items."
27
+ end
28
+
29
+ alias_method :[], :lookup
30
+ alias_method :[]=, :store
31
+
32
+ protected
33
+
34
+ def resize
35
+ @cache.delete(@cache.tail) while @cache.size > @limit
36
+ end
37
+
38
+ def miss(key, value)
39
+ @cache[key] = value
40
+
41
+ @cache.pop_tail if @cache.size > @limit
42
+
43
+ value
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,214 @@
1
+ module CacheLib
2
+ class LirsCache < BasicCache
3
+ def initialize(*args)
4
+ s_limit, q_limit = args
5
+
6
+ fail ArgumentError 'S Limit must be 1 or greater.' if
7
+ s_limit.nil? || s_limit < 1
8
+ fail ArgumentError 'Q Limit must be 1 or greater.' if
9
+ q_limit.nil? || q_limit < 1
10
+
11
+ @s_limit = s_limit
12
+ @q_limit = q_limit
13
+ @limit = s_limit + q_limit
14
+
15
+ @cache = UtilHash.new
16
+ @stack = UtilHash.new
17
+ @queue = UtilHash.new
18
+ end
19
+
20
+ def initialize_copy(source)
21
+ source_raw = source.raw
22
+
23
+ @limit = source_raw[:limit]
24
+ @s_limit = source_raw[:s_limit]
25
+ @q_limit = source_raw[:q_limit]
26
+
27
+ @cache = source_raw[:cache]
28
+ @stack = source_raw[:stack]
29
+ @queue = source_raw[:queue]
30
+ end
31
+
32
+ def limit=(args)
33
+ s_limit, q_limit = args
34
+
35
+ fail ArgumentError 'S Limit must be 1 or greater.' if
36
+ s_limit.nil? || s_limit < 1
37
+ fail ArgumentError 'Q Limit must be 1 or greater.' if
38
+ q_limit.nil? || q_limit < 1
39
+
40
+ @s_limit = s_limit
41
+ @q_limit = q_limit
42
+ @limit = s_limit + q_limit
43
+
44
+ resize
45
+ end
46
+
47
+ def get(key)
48
+ if @cache.key?(key)
49
+ hit(key)
50
+ else
51
+ miss(key, yield)
52
+ end
53
+ end
54
+
55
+ def store(key, value)
56
+ if @cache.key?(key)
57
+ @cache[key] = value
58
+ hit(key)
59
+ else
60
+ miss(key, value)
61
+ end
62
+ end
63
+
64
+ def lookup(key)
65
+ hit(key) if @cache.key?(key)
66
+ end
67
+
68
+ def fetch(key)
69
+ if @cache.key?(key)
70
+ hit(key)
71
+ else
72
+ yield if block_given?
73
+ end
74
+ end
75
+
76
+ def evict(key)
77
+ return unless @cache.key?(key)
78
+
79
+ value = @cache.delete(key)
80
+
81
+ if @queue.key?(key)
82
+ @queue.delete(key)
83
+ else
84
+ promote_hir if @queue.size > 0
85
+
86
+ trim_stack
87
+ end
88
+
89
+ value
90
+ end
91
+
92
+ def clear
93
+ @cache.clear
94
+ @stack.clear
95
+ @queue.clear
96
+ nil
97
+ end
98
+
99
+ def raw
100
+ { limit: @limit,
101
+ s_limit: @s_limit,
102
+ q_limit: @q_limit,
103
+ cache: @cache.clone,
104
+ stack: @stack.clone,
105
+ queue: @queue.clone }
106
+ end
107
+
108
+ def inspect
109
+ "#{self.class} with a limit of #{@limit}, "\
110
+ "s_limit of #{@s_limit} and q_limit of #{@q_limit} "\
111
+ "currently caching #{@cache.size} items."
112
+ end
113
+
114
+ alias_method :[], :lookup
115
+ alias_method :[]=, :store
116
+
117
+ protected
118
+
119
+ def trim_stack
120
+ s_tail = @stack.tail
121
+ while @queue.key?(s_tail) || !@cache.key?(s_tail)
122
+ @stack.delete(s_tail)
123
+ s_tail = @stack.tail
124
+ end
125
+ end
126
+
127
+ def promote_hir
128
+ key = @queue.head
129
+
130
+ @stack.set_tail(key, nil) unless @stack.key?(key)
131
+ @queue.delete(key)
132
+ end
133
+
134
+ def pop_tail
135
+ key = @queue.tail
136
+
137
+ @queue.delete(key)
138
+ @cache.delete(key)
139
+ end
140
+
141
+ def pop_stack
142
+ key = @stack.tail
143
+
144
+ @cache.delete(key)
145
+ trim_stack
146
+ end
147
+
148
+ def demote_lir
149
+ key = @stack.tail
150
+
151
+ @stack.delete(key)
152
+ @queue.set_head(key, nil)
153
+ trim_stack
154
+ end
155
+
156
+ def resize
157
+ s_size = 0
158
+
159
+ @stack.each do |key, _|
160
+ s_size += 1 if @cache.key?(key) && !@queue.key?(key)
161
+ end
162
+
163
+ promote_hir while (s_size < @s_limit && @queue.size > 0) && s_size += 1
164
+ pop_tail while @queue.size > 0 && @cache.size > @limit
165
+ pop_stack while @cache.size > @limit && s_size -= 1
166
+ demote_lir while s_size > @s_limit && s_size -= 1
167
+ end
168
+
169
+ def hit(key)
170
+ value = @cache[key]
171
+
172
+ if @stack.key?(key)
173
+ if @queue.key?(key)
174
+ @stack.refresh(key)
175
+ @queue.delete(key)
176
+
177
+ demote_lir
178
+ else
179
+ s_tail_key = @stack.tail
180
+ @stack.refresh(key)
181
+
182
+ trim_stack if s_tail_key == key
183
+ end
184
+ else
185
+ @stack.set_head(key, nil)
186
+ @queue.refresh(key)
187
+ end
188
+
189
+ value
190
+ end
191
+
192
+ def miss(key, value)
193
+ if @cache.size < @s_limit
194
+ @cache[key] = value
195
+ @stack.set_head(key, nil)
196
+ else
197
+ pop_tail if @queue.size >= @q_limit
198
+
199
+ @cache[key] = value
200
+
201
+ if @stack.key?(key)
202
+ @stack.refresh(key)
203
+
204
+ demote_lir
205
+ else
206
+ @stack.set_head(key, nil)
207
+ @queue.set_head(key, nil)
208
+ end
209
+ end
210
+
211
+ value
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,82 @@
1
+ module CacheLib
2
+ class LruCache < BasicCache
3
+ def initialize(*args)
4
+ limit, _ = args
5
+
6
+ fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
7
+ limit.nil? || limit < 1
8
+
9
+ @limit = limit
10
+
11
+ @cache = UtilHash.new
12
+ end
13
+
14
+ def limit=(args)
15
+ limit, _ = args
16
+
17
+ fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
18
+ limit.nil? || limit < 1
19
+
20
+ @limit = limit
21
+
22
+ resize
23
+ end
24
+
25
+ def get(key)
26
+ has_key = true
27
+ value = @cache.delete(key) { has_key = false }
28
+ if has_key
29
+ @cache[key] = value
30
+ else
31
+ miss(key, yield)
32
+ end
33
+ end
34
+
35
+ def store(key, value)
36
+ @cache.delete(key)
37
+ miss(key, value)
38
+ end
39
+
40
+ def lookup(key)
41
+ has_key = true
42
+ value = @cache.delete(key) { has_key = false }
43
+ @cache[key] = value if has_key
44
+ end
45
+
46
+ def fetch(key)
47
+ has_key = true
48
+ value = @cache.delete(key) { has_key = false }
49
+ if has_key
50
+ @cache[key] = value
51
+ else
52
+ yield if block_given?
53
+ end
54
+ end
55
+
56
+ def inspect
57
+ "#{self.class} with a limit of #{@limit} "\
58
+ "currently caching #{@cache.size} items."
59
+ end
60
+
61
+ alias_method :[], :lookup
62
+ alias_method :[]=, :store
63
+
64
+ protected
65
+
66
+ def resize
67
+ @cache.pop_tail while @cache.size > @limit
68
+ end
69
+
70
+ def hit(key)
71
+ @cache.refresh(key)
72
+ end
73
+
74
+ def miss(key, value)
75
+ @cache[key] = value
76
+
77
+ @cache.pop_tail if @cache.size > @limit
78
+
79
+ value
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'safe_sync'
2
+
3
+ module CacheLib
4
+ class SafeBasicCache < BasicCache
5
+ include SafeSync
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'safe_sync'
2
+
3
+ module CacheLib
4
+ class SafeFifoCache < FifoCache
5
+ include SafeSync
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'safe_sync'
2
+
3
+ module CacheLib
4
+ class SafeLirsCache < LirsCache
5
+ include SafeSync
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'safe_sync'
2
+
3
+ module CacheLib
4
+ class SafeLruCache < LruCache
5
+ include SafeSync
6
+ end
7
+ end