> sorry, don't know how that's running on linux in the first place... you need:
>
> --- a.rb 2005-12-31 20:30:59.000000000 -0700
> +++ b.rb 2005-12-31 20:31:10.000000000 -0700
> @@ -1,9 +1,10 @@
> def memoize_to_file(name, file)
> + meth = method(name)
> cache = Hash.new.update(Marshal.load(File.read(file))) rescue {}
> (class << self; self; end).class_eval do
> define_method(name) do |*args|
> unless cache.has_key?(args)
> - cache[args] = method(name).call(*args)
> + cache[args] = meth.call(*args)
> File.open(file, "wb+"){|f| Marshal.dump(cache, f) }
> end
> cache[args]
[...]
Thanks, that did the trick nicely. It's now part of memoize 1.2.0.
Dumping the cache on every miss seems expensive.
I changed the code to save it once every N misses, and made the cache update
atomic to prevent problems with:
* concurrent executions of the memoized method (processes and threads)
* no HD space being left (the previous cache state is preserved)
I also applied those changes to memoize.rb 1.2.0 and generalized it to work
with instance methods, see the patch after memo.rb.
batsman@tux-chan:/tmp$ cat memo.rb
def memoize_to_file(name, file = nil, period = -1)
meth = method(name)
cache = Hash.new.update(Marshal.load(File.read(file))) rescue {}
save_cache_proc = lambda do
tmpfile = "#{file}.#{Process.pid}"
begin
File.open(tmpfile, "wb+"){|f| Marshal.dump(cache, f) }
File.rename tmpfile, file
rescue Exception
end
end
at_exit { save_cache_proc.call } if file
calls = period
(class << self; self; end).class_eval do
define_method(name) do |*args|
unless cache.has_key?(args)
cache[args] = meth.call(*args)
if file && period != -1 && (calls -= 1) <= 0
calls = period
save_cache_proc.call
end
end
cache[args]
end
end
end
def fib(n)
return n if n < 2
fib(n-1) + fib(n-2)
end
def fib2(n)
return n if n < 2
fib2(n-1) + fib2(n-2)
end
def fib3(n)
return n if n < 2
fib3(n-1) + fib3(n-2)
end
memoize_to_file "fib", "fib.cache", 1
memoize_to_file "fib2", "fib2.cache"
memoize_to_file "fib3", "fib3.cache", 10
require 'benchmark'
Benchmark.bmbm(10) do |bm|
bm.report("period 1") { fib 1000 }
bm.report("period 10"){ fib3 1000 }
bm.report("at_exit") { fib2 1000 }
end
batsman@tux-chan:/tmp$ ruby memo.rb
Rehearsal ---------------------------------------------
period 1 4.030000 0.510000 4.540000 ( 5.507344)
period 10 0.430000 0.060000 0.490000 ( 0.570224)
at_exit 0.030000 0.000000 0.030000 ( 0.039652)
------------------------------------ total: 5.060000sec
user system total real
period 1 0.000000 0.000000 0.000000 ( 0.000035)
period 10 0.000000 0.000000 0.000000 ( 0.000033)
at_exit 0.000000 0.000000 0.000000 ( 0.000035)
The patch to memoize.rb:
--- memoize.rb.orig 2006-01-02 11:02:54.000000000 +0100
+++ memoize.rb 2006-01-02 12:31:46.000000000 +0100
@@ -1,39 +1,106 @@
module Memoize
MEMOIZE_VERSION = "1.2.0"
memoize.rb (2.43 KB)
···
On Sun, Jan 01, 2006 at 09:32:56PM +0900, Daniel Berger wrote:
-
+
+ def self.memoize(recv, name, file=nil, period=-1)
+ class << recv; self end.instance_eval{ instance_memoize(name, file, period) }
+ end
+
+ def memoize(name, file=nil, period=-1)
+ Memoize.memoize(self, name, file, period)
+ end
+end
+
+class Module
# Memoize the method +name+. If +file+ is provided, then the method results
- # are stored on disk rather than in memory. This consumes virtually no
- # memory and is persistant.
- def memoize(name, file=nil)
- meth = method(name)
-
+ # are stored on disk in addition to the in-memory cache.
+ def instance_memoize(name, file=nil, period=-1)
+ meth = instance_method(name)
+
if file
cache = Hash.new.update(Marshal.load(File.read(file))) rescue {}
else
cache = {}
end
-
- if file
- (class << self; self; end).class_eval do
- define_method(name) do |*args|
- unless cache.has_key?(args)
- cache[args] = meth.call(*args)
- File.open(file, "wb+"){|f| Marshal.dump(cache, f) }
- end
- cache[args]
- end
+
+ save_cache_proc = lambda do
+ tmpfile = "#{file}.#{Process.pid}"
+ begin
+ File.open(tmpfile, "wb+"){|f| Marshal.dump(cache, f) }
+ File.rename tmpfile, file
+ rescue Exception
end
- else
- (class << self; self; end).class_eval do
- define_method(name) do |*args|
- if cache.has_key?(args)
- cache[args]
- else
- cache[args] ||= meth.call(*args)
- end
+ end
+
+ at_exit { save_cache_proc.call } if file
+
+ calls = period
+ define_method(name) do |*args|
+ unless cache.has_key?(args)
+ cache[args] = meth.bind(self).call(*args)
+ if file && period != -1 && (calls -= 1) <= 0
+ calls = period
+ save_cache_proc.call
end
end
+ cache[args]
end
+
cache
end
-end
\ No newline at end of file
+end
+
+if __FILE__ == $0
+ def fib(n)
+ return n if n < 2
+ fib(n-1) + fib(n-2)
+ end
+
+ def fib2(n)
+ return n if n < 2
+ fib2(n-1) + fib2(n-2)
+ end
+
+ def fib3(n)
+ return n if n < 2
+ fib3(n-1) + fib3(n-2)
+ end
+
+ include Memoize
+
+ module DumbFib
+ def fib(n)
+ return n if n < 2
+ fib(n-1) + fib(n-2)
+ end
+ end
+
+ DumbFib2 = DumbFib.clone
+
+ memoize :fib, "xfib.memoize.cache", 1
+ memoize :fib2, "xfib2.memoize.cache"
+ memoize :fib3, "xfib3.memoize.cache", 10
+
+ class Dummy1
+ include DumbFib
+ instance_memoize :fib, "xfib.dumb1.cache"
+ end
+ class Dummy2
+ include DumbFib2
+ end
+ module DumbFib2
+ instance_memoize :fib, "xfib.dumb2.cache"
+ end
+
+ require 'benchmark'
+ runs = 1
+ Benchmark.bmbm(10) do |bm|
+ dummy1 = Dummy1.new
+ dummy2 = Dummy2.new
+ bm.report("period 1") { runs.times{ fib 500 } }
+ bm.report("period 10"){ runs.times{ fib3 500 } }
+ bm.report("at_exit") { runs.times{ fib2 500 } }
+ bm.report("at_exit''") { runs.times{ dummy1.fib 500 } }
+ bm.report("at_exit'''") { runs.times{ dummy2.fib 500 }; runs = 100000}
+ end
+
+end
batsman@tux-chan:/tmp$ ruby memoize.rb
Rehearsal ----------------------------------------------
period 1 0.870000 0.160000 1.030000 ( 1.247975)
period 10 0.100000 0.010000 0.110000 ( 0.151390)
at_exit 0.010000 0.000000 0.010000 ( 0.034512)
at_exit'' 0.020000 0.000000 0.020000 ( 0.030174)
at_exit''' 0.010000 0.000000 0.010000 ( 0.030094)
------------------------------------- total: 1.180000sec
user system total real
period 1 0.490000 0.000000 0.490000 ( 0.548635)
period 10 0.500000 0.000000 0.500000 ( 0.539279)
at_exit 0.500000 0.000000 0.500000 ( 0.562339)
at_exit'' 0.490000 0.000000 0.490000 ( 0.534087)
at_exit''' 0.490000 0.000000 0.490000 ( 0.541301)
batsman@tux-chan:/tmp$ ruby memoize.rb
Rehearsal ----------------------------------------------
period 1 0.000000 0.000000 0.000000 ( 0.000048)
period 10 0.000000 0.000000 0.000000 ( 0.000019)
at_exit 0.000000 0.000000 0.000000 ( 0.000017)
at_exit'' 0.000000 0.000000 0.000000 ( 0.000019)
at_exit''' 0.000000 0.000000 0.000000 ( 0.000018)
------------------------------------- total: 0.000000sec
user system total real
period 1 0.480000 0.000000 0.480000 ( 0.533512)
period 10 0.490000 0.000000 0.490000 ( 0.542617)
at_exit 0.500000 0.000000 0.500000 ( 0.544705)
at_exit'' 0.500000 0.000000 0.500000 ( 0.551834)
at_exit''' 0.490000 0.000000 0.490000 ( 0.538747)
I also attached the full sources for your convenience.
--
Mauricio Fernandez