persistent_memoize 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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in persistent_memoize.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2013 Neil Kandalgaonkar
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject
11
+ to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
@@ -0,0 +1,133 @@
1
+ # PersistentMemoize
2
+
3
+ ## Description
4
+ Speed up methods at the cost of disk space. Keep caches on disk to speed
5
+ up later invocations of your program.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'persistent_memoize'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install persistent_memoize
19
+
20
+ ## Synopsis
21
+
22
+ Let's imagine we have a script, _fib.rb_:
23
+
24
+ def fib(n)
25
+ return n if n < 2
26
+ fib(n-1) + fib(n-2)
27
+ end
28
+
29
+ puts fib(40)
30
+
31
+ Executing it is slow - on my machine, this is 20 seconds!
32
+
33
+ $ time ruby fib.rb
34
+ 102334155
35
+
36
+ real 0m20.107s
37
+ user 0m20.067s
38
+ sys 0m0.034s
39
+
40
+ So let's add memoization.
41
+
42
+ require 'persistent_memoize'
43
+ include PersistentMemoize
44
+
45
+ def fib(n)
46
+ return n if n < 2
47
+ fib(n-1) + fib(n-2)
48
+ end
49
+
50
+ memoize(:fib, './fib-cache')
51
+ puts fib(40)
52
+
53
+ And executing it now takes about a tenth of a second.
54
+
55
+ $ time ruby ./fib.rb
56
+ 102334155
57
+
58
+ real 0m0.102s
59
+ user 0m0.062s
60
+ sys 0m0.036s
61
+
62
+ What's more, it stays fast - subsequent invocations after the first one are even faster.
63
+
64
+ $ time ruby ./fib.rb
65
+ 102334155
66
+
67
+ real 0m0.092s
68
+ user 0m0.060s
69
+ sys 0m0.029s
70
+
71
+ ## Motivation
72
+
73
+ This library is most useful when you have a program that runs repeatedly,
74
+ but which has to incorporate calculations or API results that rarely
75
+ change.
76
+
77
+ I use it to rebuild a static blog, which is based on my postings
78
+ on other sites, fetched via API. If I tweak the look of the blog, I don't want to have to
79
+ fetch all those postings all over again. So it's a quick modification
80
+ to the library that looks up those results, to cache them locally, via
81
+ memoization. If I know that those blog postings have changed, then I
82
+ delete the cache, manually, and then it regenerates from scratch.
83
+
84
+ ## Caveats
85
+
86
+ As with all memoization, the method memoized must be _pure_, that
87
+ is, the result depends solely on its arguments. Memoizing a function which
88
+ returns the current date will give you the wrong answer tomorrow.
89
+
90
+ This can't magically make every method faster; it's all trade-offs. The
91
+ thing-to-be-memoized should be more expensive than computing a hash, and
92
+ deserializing data from disk. Otherwise memoizing will make it slower.
93
+
94
+ This depends on arguments having a unique serialization via the Marshal
95
+ library. Certain Ruby constructs cannot be serialized in this way and
96
+ may raise TypeErrors. See the
97
+ [Marshal documentation](http://www.ruby-doc.org/core-2.0/Marshal.html) for details.
98
+
99
+
100
+ ## Constants
101
+ PersistentMemoize::PERSISTENT_MEMOIZE_VERSION
102
+
103
+ Returns the version of this package as a String.
104
+
105
+ ## Methods
106
+ PersistentMemoize#memoize(method, path)
107
+ Takes a _method_ (symbol) and caches the results of _method_, for
108
+ particular arguments, on disk, in files under _path_.
109
+
110
+ If you call _method_ again with the same arguments, _memoize_ gives
111
+ you the value from disk instead of letting the method compute the
112
+ value again.
113
+
114
+ ## Acknowledgements
115
+
116
+ Based on memoize by Daniel Berg (https://github.com/djberg96/memoize)
117
+
118
+ Daniel's library has a file-oriented cache but it creates a single cache file per
119
+ method, containing all the cached results. For my workloads, this cache file
120
+ gets very large and rewriting it is slow. So persistent_memoize stores each result
121
+ in its own file.
122
+
123
+ Daniel also included this note in his code, so I might as well acknowledge
124
+ these people too:
125
+
126
+ Code borrowed from Nobu Nakada (ruby-talk:155159).
127
+ Code borrowed from Ara Howard (ruby-talk:173428).
128
+ Code borrowed from Andrew Johnson (http://tinyurl.com/8ymx8)
129
+ Ideas taken from Brian Buckley and Sean O'Halpin.
130
+ Tiny URL provided for Andrew Johnson because I could not find the ruby-talk
131
+ reference. The gateway may have been broken at the time.
132
+
133
+
@@ -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.verbose = true
7
+ t.warning = true
8
+ end
@@ -0,0 +1,41 @@
1
+ ###################################################################
2
+ # fibonacci.rb
3
+ #
4
+ # Demonstrates, via Benchmark, the difference between a memoized
5
+ # version of the fibonnaci method versus a non-memoized version.
6
+ #
7
+ # You can run this via the 'example_fib' rake task.
8
+ ###################################################################
9
+ require 'benchmark'
10
+ require 'fileutils'
11
+ require 'persistent_memoize'
12
+ include PersistentMemoize
13
+
14
+ # Our fibonacci function
15
+ def fib(n)
16
+ return n if n < 2
17
+ fib(n-1) + fib(n-2)
18
+ end
19
+
20
+ file = File.join((ENV['HOME'] || ENV['USERPROFILE']), 'fib.cache')
21
+
22
+ max_iter = ARGV[0].to_i
23
+ max_fib = ARGV[1].to_i
24
+
25
+ max_iter = 100 if max_iter == 0
26
+ max_fib = 25 if max_fib == 0
27
+
28
+ print "\nBenchmarking against version: " + PERSISTENT_MEMOIZE_VERSION + "\n\n"
29
+
30
+ Benchmark.bm(35) do |x|
31
+ x.report("Not memoized:"){
32
+ max_iter.times{ fib(max_fib) }
33
+ }
34
+
35
+ x.report("Memoized to file:"){
36
+ memoize(:fib, file)
37
+ max_iter.times{ fib(max_fib) }
38
+ }
39
+ end
40
+
41
+ FileUtils.remove_dir(file) if File.exists?(file)
@@ -0,0 +1,26 @@
1
+ require 'digest/md5'
2
+ require 'fileutils'
3
+ require "persistent_memoize/version"
4
+
5
+ module PersistentMemoize
6
+ # Memoize the method 'name', with results stored in files under 'path'
7
+ def memoize(name, path)
8
+ unless File.exists?(path)
9
+ FileUtils.mkdir_p(path)
10
+ end
11
+
12
+ (class<<self; self; end).send(:define_method, name) do |*args|
13
+ key = Digest::MD5.hexdigest(Marshal.dump(args))
14
+ cacheFile = File.join(path, key)
15
+ if File.exists?(cacheFile)
16
+ results = File.open(cacheFile, 'rb'){ |f| Marshal.load(f) }
17
+ else
18
+ results = super(*args)
19
+ File.open(cacheFile, 'wb'){ |f| Marshal.dump(results, f) }
20
+ end
21
+ results
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,3 @@
1
+ module PersistentMemoize
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'persistent_memoize/version'
5
+
6
+ # Build the gem with the 'rake gem:build' command.
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'persistent_memoize'
10
+ spec.version = PersistentMemoize::VERSION
11
+ spec.version = '0.0.1'
12
+ spec.authors = ['Neil Kandalgaonkar']
13
+ spec.license = 'MIT'
14
+ spec.email = ['neilk@brevity.org']
15
+ spec.homepage = 'https://github.com/neilk/persistent_memoize'
16
+ spec.platform = Gem::Platform::RUBY
17
+ spec.summary = 'Speeds up methods with caches, which persistent on disk'
18
+ spec.has_rdoc = true
19
+ spec.files = `git ls-files`.split($/)
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.extra_rdoc_files = ['README.md']
25
+
26
+ spec.add_development_dependency('test-unit', '>= 2.0.2')
27
+ spec.add_development_dependency "bundler", "~> 1.3"
28
+ spec.add_development_dependency "rake"
29
+
30
+ spec.description = <<-EOF
31
+ This allows you to cache method calls for faster execution, at the cost of
32
+ storage space and IO operations. The caches are kept on disk,
33
+ persistent between executions of the program.
34
+ EOF
35
+ end
@@ -0,0 +1,54 @@
1
+ ###############################################
2
+ # test_memoize.rb
3
+ #
4
+ # Test suite for the memoize library.
5
+ ###############################################
6
+ require 'rubygems'
7
+ gem 'test-unit'
8
+
9
+ require 'test/unit'
10
+ require 'fileutils'
11
+ require 'persistent_memoize'
12
+
13
+ class TC_PersistentMemoize < Test::Unit::TestCase
14
+ include PersistentMemoize
15
+
16
+ def setup
17
+ @path = File.join((ENV['HOME'] || ENV['USERPROFILE']), 'test.cache')
18
+ end
19
+
20
+ def add(x, y)
21
+ return x + y
22
+ end
23
+
24
+ def fib(n)
25
+ return n if n < 2
26
+ fib(n-1) + fib(n-2)
27
+ end
28
+
29
+ def factorial(n)
30
+ f = 1
31
+ n.downto(2) { |x| f *= x }
32
+ f
33
+ end
34
+
35
+ def test_memoize
36
+ assert_nothing_raised{ fib(5) }
37
+ assert_nothing_raised{ memoize(:fib, @path) }
38
+ assert_nothing_raised{ fib(50) }
39
+ assert_equal(55, fib(10))
40
+ end
41
+
42
+ def test_memoize_directory_properties
43
+ assert(!File.exists?(@path), "path did not exist")
44
+ assert_nothing_raised{ memoize(:add, @path) }
45
+ assert(File.exists?(@path), "path does exist")
46
+ assert(Dir.entries(@path).select{ |f| f != '.' and f != '..' }.length == 0, "path is empty of files")
47
+ assert_nothing_raised{ add(10, 20) }
48
+ assert(Dir.entries(@path).select{ |f| f != '.' and f != '..' }.length == 1, "path has exactly one file")
49
+ end
50
+
51
+ def teardown
52
+ FileUtils.remove_dir(@path) if File.exists?(@path)
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: persistent_memoize
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Neil Kandalgaonkar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: test-unit
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.0.2
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.0.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.3'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: ! " This allows you to cache method calls for faster execution, at
63
+ the cost of \n storage space and IO operations. The caches are kept on disk,\n
64
+ \ persistent between executions of the program. \n"
65
+ email:
66
+ - neilk@brevity.org
67
+ executables: []
68
+ extensions: []
69
+ extra_rdoc_files:
70
+ - README.md
71
+ files:
72
+ - .gitignore
73
+ - Gemfile
74
+ - LICENSE.txt
75
+ - README.md
76
+ - Rakefile
77
+ - examples/example_fibonacci.rb
78
+ - lib/persistent_memoize.rb
79
+ - lib/persistent_memoize/version.rb
80
+ - persistent_memoize.gemspec
81
+ - test/test_memoize.rb
82
+ homepage: https://github.com/neilk/persistent_memoize
83
+ licenses:
84
+ - MIT
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.23
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Speeds up methods with caches, which persistent on disk
107
+ test_files:
108
+ - test/test_memoize.rb