persistent_memoize 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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