cache_lib 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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