caches 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5b755f8f05416c7bd3e24c8ba950f77ac2daea8c
4
- data.tar.gz: a698c0d6cfeaf897adef53e6f13b028e2531ce1d
3
+ metadata.gz: 89ecbca50c8eeca4f3a3ba844fc8af31051e0aca
4
+ data.tar.gz: 7bb00ffe94dd6f21767037d14217223e7f4bad86
5
5
  SHA512:
6
- metadata.gz: 639943fc289cfe0bb9e78c52671ca55f6908b2ec282da95222b60303a15138f3274b18ce8627de05a22f0d233d31430dc5196df3d8b475917dfe8b23a7b56cc4
7
- data.tar.gz: 890232456a3682cb1883332c317cc0cfb68868801ef468f17faa7f0e4d130dec53590e61820e42176506210fa4e6e5397c38349c47834d35c662efdd396d5d76
6
+ metadata.gz: afa402ae0b268873414fbd05f13a9c1d15982272523ab3e70d6badd37b6d05d03bf5054e774e1f17cfee93317460839018766fd76b7d8eb286b118821acfaadd
7
+ data.tar.gz: 36e84e288dc638a1c0b184136965fba84b737d243d81f334a0feddd356c95d3fb16631336bed1b0770ed76e29635e5d34390418ffa57fa7846083b1b4c6c594c
data/CHANGELOG.md CHANGED
@@ -1,11 +1,14 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## Unreleased
4
+ ## 0.0.4 - 2015-03-03
5
5
 
6
6
  ### Added
7
7
 
8
- Nothing
8
+ - `Caches::TTL` supports `#delete(key)`
9
+ - Added a CONTRIBUTING guide
10
+ - Benchmarking with Ruby 2.1.3
11
+ - `Caches::LRU` can handle `max_keys: 0`
9
12
 
10
13
  ### Changed
11
14
  Nothing
@@ -15,4 +18,4 @@ Nothing
15
18
 
16
19
  ## 0.0.3 - 2014-08-22
17
20
 
18
- Currently have Caches::TTL and Caches::LRU
21
+ Currently have `Caches::TTL` and `Caches::LRU`
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,11 @@
1
+ # Contributing
2
+
3
+ The goal of this gem is to provide a small collection of useful, performant caches.
4
+
5
+ - To add a new kind of cache, you must convince us that it's useful
6
+ - To change a cache, you must
7
+ - convince us that it's a useful change
8
+ - ensure that it's well-tested
9
+ - ensure that it's benchmarked and performant
10
+
11
+ `rake` runs the specs and `rake benchmark` runs the benchmarks (still in-progress). They are expected to pass on Ruby 2.1.3
data/README.md CHANGED
@@ -49,7 +49,7 @@ h.memoize(:a) { |k| calculation_for(k) }
49
49
 
50
50
  ### Caches::LRU
51
51
 
52
- LRU (Least Recently Used) remembers as many keys as you tell it to, dropping the least recently used key on each insert after its limit is reached.
52
+ LRU (Least Recently Used) remembers as many keys as you tell it to, dropping the least recently used key on each insert after its limit is reached. (Inserts and reads count as a usage; updates do not.)
53
53
 
54
54
  ```ruby
55
55
  require 'caches/lru'
@@ -77,11 +77,7 @@ Or install it yourself as:
77
77
 
78
78
  ## Contributing
79
79
 
80
- 1. Fork it
81
- 2. Create your feature branch (`git checkout -b my-new-feature`)
82
- 3. Commit your changes (`git commit -am 'Add some feature'`)
83
- 4. Push to the branch (`git push origin my-new-feature`)
84
- 5. Create new Pull Request
80
+ See CONTRIBUTING.md
85
81
 
86
82
  # Thanks
87
83
 
data/Rakefile CHANGED
@@ -1,11 +1,18 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rspec/core/rake_task'
3
3
 
4
+ task :default => :spec
5
+
4
6
  desc "Run the specs in documentation format"
5
7
  RSpec::Core::RakeTask.new(:spec) do |t|
6
8
  t.rspec_opts = '--format documentation'
7
9
  end
8
10
 
11
+ desc "Run the benchmarks"
12
+ RSpec::Core::RakeTask.new(:benchmark) do |t|
13
+ t.rspec_opts = '--format documentation --tag benchmark'
14
+ end
15
+
9
16
  task :console do
10
17
  require 'irb'
11
18
  require 'irb/completion'
data/caches.gemspec CHANGED
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "rspec", "~> 2.14"
24
24
 
25
+ spec.add_development_dependency "benchmark-ips", "~> 2.0"
25
26
  end
data/lib/caches/lru.rb CHANGED
@@ -13,6 +13,7 @@ module Caches
13
13
  end
14
14
 
15
15
  def [](key)
16
+ return nil if max_keys.zero?
16
17
  return nil unless data.has_key?(key)
17
18
  value, node = data[key]
18
19
  keys.move_to_head(node)
@@ -20,6 +21,7 @@ module Caches
20
21
  end
21
22
 
22
23
  def []=(key, val)
24
+ return nil if max_keys.zero?
23
25
  if data.has_key?(key)
24
26
  data[key][0] = val
25
27
  else
data/lib/caches/ttl.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'time'
2
2
  require_relative 'accessible'
3
+ require_relative 'linked_list'
3
4
 
4
5
  module Caches
5
6
  class TTL
@@ -16,13 +17,12 @@ module Caches
16
17
 
17
18
  def [](key)
18
19
  return nil unless data.has_key?(key)
19
- if current?(key)
20
- data[key][:time] = current_time if refresh
21
- data[key][:value]
20
+ if current?(key)
21
+ data[key][:value].tap {
22
+ data[key][:time] = current_time if refresh
23
+ }
22
24
  else
23
- node = data[key][:node]
24
- nodes.delete(node)
25
- data.delete(key)
25
+ delete(key)
26
26
  nil
27
27
  end
28
28
  end
@@ -41,8 +41,15 @@ module Caches
41
41
  data[key] = {time: current_time, value: val, node: node}
42
42
  end
43
43
 
44
+ def delete(key)
45
+ node = data[key][:node]
46
+ nodes.delete(node)
47
+ hash = data.delete(key)
48
+ hash.fetch(:value)
49
+ end
50
+
44
51
  def size
45
- keys.length
52
+ nodes.length
46
53
  end
47
54
 
48
55
  def keys
@@ -1,3 +1,3 @@
1
1
  module Caches
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'benchmark/ips'
3
+ require_relative '../lib/caches/all'
4
+ require 'stringio'
5
+
6
+ describe "benchmarks", benchmark: true do
7
+
8
+ # Comment to unmute the output from Benchmark
9
+ before(:all) { $stdout = StringIO.new }
10
+ after(:all) { $stdout = STDOUT }
11
+
12
+ let(:klass) { described_class }
13
+
14
+ # These are both somewhat arbitrary and may depend on Ruby version
15
+ # Tested with Ruby 2.1.3
16
+ let(:reasonable_ratio) { 1.1 }
17
+ let(:reasonable_percent_variation) { 30 }
18
+
19
+ describe Caches::TTL do
20
+
21
+ let!(:small_range) { (1..10) }
22
+ let!(:small_cache) {
23
+ klass.new.tap { |sc| small_range.each {|key| sc[key] = key } }
24
+ }
25
+ let(:big_range) { (1..1_000_000) }
26
+ let(:big_cache) {
27
+ klass.new.tap { |sc| big_range.each {|key| sc[key] = key } }
28
+ }
29
+
30
+ let(:very_big_range) { 1..10_000_000 }
31
+ let(:very_big_cache) {
32
+ klass.new.tap { |sc| very_big_range.each { |key| sc[key] = key } }
33
+ }
34
+
35
+ it "has constant time reads" do
36
+ big_cache[1] # force creation
37
+ results = {}
38
+ report = Benchmark.ips do |x|
39
+ small_cache_test_keys = small_range.to_a.shuffle.cycle
40
+ big_cache_test_keys = big_range.to_a.shuffle.cycle
41
+ results[:small] = x.report("reading from small cache") {
42
+ small_cache[small_cache_test_keys.next]
43
+ }
44
+ results[:big] = x.report("reading from big cache") {
45
+ big_cache[big_cache_test_keys.next]
46
+ }
47
+ end
48
+ small_ips = report.entries.first.ips
49
+ big_ips = report.entries.last.ips
50
+ expect(big_ips).to be < (small_ips * reasonable_ratio)
51
+ end
52
+
53
+ it "has constant time inserts" do
54
+ write_keys = (1..10_000_000).to_a.shuffle.cycle
55
+ report = Benchmark.ips do |x|
56
+ x.report("inserts") { small_cache[write_keys.next] = :a }
57
+ end
58
+ rand_report = report.entries.first
59
+
60
+ variance = rand_report.stddev_percentage
61
+ expect(variance).to be < reasonable_percent_variation
62
+ end
63
+
64
+ it "has constant time updates" do
65
+ write_keys = small_range.to_a.cycle
66
+ report = Benchmark.ips do |x|
67
+ x.report("updates") { small_cache[write_keys.next] = :a }
68
+ end
69
+ rand_report = report.entries.first
70
+
71
+ variance = rand_report.stddev_percentage
72
+ expect(variance).to be < reasonable_percent_variation
73
+ end
74
+
75
+ # Using a very big cache here in hopes that, even though IPS does as many
76
+ # deletes as it possibly can during its runtime, it won't finish deleting
77
+ # all the cache keys before it finishes
78
+ it "has constant time deletes" do
79
+ delete_keys = very_big_range.to_a.each
80
+ very_big_cache[1] # force creation
81
+ report = Benchmark.ips do |x|
82
+ x.report("deletes") { very_big_cache.delete(delete_keys.next) }
83
+ end
84
+ rand_report = report.entries.first
85
+
86
+ variance = rand_report.stddev_percentage
87
+ expect(variance).to be < reasonable_percent_variation
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -62,7 +62,8 @@ describe Caches::LRU do
62
62
 
63
63
  it "does not calculate the value" do
64
64
  expect(greeting).not_to receive(:upcase)
65
- cache.memoize(:c) { greeting.upcase }
65
+ val = cache.memoize(:c) { greeting.upcase }
66
+ expect(val).to eq("Caspian")
66
67
  end
67
68
 
68
69
  end
@@ -72,8 +73,9 @@ describe Caches::LRU do
72
73
  let(:key) { :nonexistent }
73
74
 
74
75
  it "calculates the value" do
75
- expect(greeting).to receive(:upcase)
76
- cache.memoize(:nonexistent) { greeting.upcase }
76
+ expect(greeting).to receive(:upcase).and_call_original
77
+ val = cache.memoize(:nonexistent) { greeting.upcase }
78
+ expect(val).to eq("HI")
77
79
  end
78
80
 
79
81
  it "sets the value" do
@@ -104,5 +106,22 @@ describe Caches::LRU do
104
106
 
105
107
  end
106
108
 
109
+ describe "when max_keys is 0" do
110
+
111
+ let(:options) { {max_keys: 0} }
112
+ let(:greeting) { 'hi' }
113
+
114
+ it "doesn't store anything" do
115
+ expect(cache[:a]).to eq(nil)
116
+ end
117
+
118
+ it "always runs a memoize block" do
119
+ expect(greeting).to receive(:upcase).and_call_original
120
+ val = cache.memoize(:a) { greeting.upcase }
121
+ expect(val).to eq("HI")
122
+ end
123
+
124
+ end
125
+
107
126
  end
108
127
 
@@ -7,7 +7,7 @@ describe Caches::TTL do
7
7
  let(:ttl) { 0.01 }
8
8
  let(:most_of_ttl) { ttl * 0.8 }
9
9
  let(:options) { { ttl: ttl} }
10
- let(:cache) {
10
+ let!(:cache) {
11
11
  described_class.new(options).tap {|c|
12
12
  c[:a] = 'Aravis'
13
13
  c[:b] = 'Bern'
@@ -19,24 +19,36 @@ describe Caches::TTL do
19
19
  expect(cache.size).to eq(3)
20
20
  end
21
21
 
22
- it "remembers cached values before the TTL expires" do
23
- expect(cache[:c]).to eq('Caspian')
22
+ it "can delete keys" do
23
+ expect(cache.keys).to eq(%i[a b c])
24
24
  expect(cache.size).to eq(3)
25
25
  # testing private method
26
26
  expect(cache.send(:nodes).length).to eq(3)
27
- end
28
27
 
29
- it "forgets cached values when a read is attempted after the TTL expires" do
30
- expect(cache[:c]).to eq('Caspian')
31
- sleep(ttl)
32
- expect(cache[:c]).to be_nil
28
+ deleted = cache.delete(:b)
29
+ expect(deleted).to eq('Bern')
30
+
31
+ expect(cache.keys).to eq(%i[a c])
33
32
  expect(cache.size).to eq(2)
34
33
  # testing private method
35
34
  expect(cache.send(:nodes).length).to eq(2)
35
+ end
36
+
37
+ it "remembers cached values before the TTL expires" do
38
+ expect(cache).not_to receive(:delete)
39
+ expect(cache[:c]).to eq('Caspian')
40
+ expect(cache.size).to eq(3)
41
+ end
42
+
43
+ it "forgets cached values when a read is attempted after the TTL expires" do
44
+ sleep(ttl)
45
+ expect(cache).to receive(:delete).with(:a).and_call_original
36
46
  expect(cache[:a]).to be_nil
47
+ expect(cache.size).to eq(2)
48
+
49
+ expect(cache).to receive(:delete).with(:c).and_call_original
50
+ expect(cache[:c]).to be_nil
37
51
  expect(cache.size).to eq(1)
38
- # testing private method
39
- expect(cache.send(:nodes).length).to eq(1)
40
52
  end
41
53
 
42
54
  describe "listing keys and values" do
data/spec/spec_helper.rb CHANGED
@@ -3,4 +3,5 @@ require 'bundler/setup'
3
3
 
4
4
  RSpec.configure do |config|
5
5
  config.order = :random
6
+ config.filter_run_excluding benchmark: true
6
7
  end
metadata CHANGED
@@ -1,57 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caches
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Long
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-22 00:00:00.000000000 Z
11
+ date: 2015-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.3'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '2.14'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: benchmark-ips
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
55
69
  description: A small collection of caches with good performance and hash-like access
56
70
  patterns
57
71
  email:
@@ -60,8 +74,9 @@ executables: []
60
74
  extensions: []
61
75
  extra_rdoc_files: []
62
76
  files:
63
- - .gitignore
77
+ - ".gitignore"
64
78
  - CHANGELOG.md
79
+ - CONTRIBUTING.md
65
80
  - Gemfile
66
81
  - LICENSE.txt
67
82
  - README.md
@@ -75,6 +90,7 @@ files:
75
90
  - lib/caches/lru.rb
76
91
  - lib/caches/ttl.rb
77
92
  - lib/caches/version.rb
93
+ - spec/benchmark_spec.rb
78
94
  - spec/caches/linked_list_spec.rb
79
95
  - spec/caches/lru_spec.rb
80
96
  - spec/caches/ttl_spec.rb
@@ -90,21 +106,22 @@ require_paths:
90
106
  - lib
91
107
  required_ruby_version: !ruby/object:Gem::Requirement
92
108
  requirements:
93
- - - '>='
109
+ - - ">="
94
110
  - !ruby/object:Gem::Version
95
111
  version: '0'
96
112
  required_rubygems_version: !ruby/object:Gem::Requirement
97
113
  requirements:
98
- - - '>='
114
+ - - ">="
99
115
  - !ruby/object:Gem::Version
100
116
  version: '0'
101
117
  requirements: []
102
118
  rubyforge_project:
103
- rubygems_version: 2.0.14
119
+ rubygems_version: 2.2.2
104
120
  signing_key:
105
121
  specification_version: 4
106
122
  summary: Caches with hash-like access
107
123
  test_files:
124
+ - spec/benchmark_spec.rb
108
125
  - spec/caches/linked_list_spec.rb
109
126
  - spec/caches/lru_spec.rb
110
127
  - spec/caches/ttl_spec.rb