From: Phrogz Date: 2010-04-18T11:40:06+09:00 Subject: Re: Memoize and GC On Apr 13, 11:28 am, Caleb Clausen wrote: > But rather than either an array or hash, what if you could write: > >   FOO=Cache.new > > That is, FOO would behave mostly like a hash, but unused entries would > age out after a while. Now if only someone would invent an appropriate > Cache class. Undocumented and without a test framework, I offer you a class I wrote a while ago that's still live and in use a couple of years later. It's not exactly what you're asking for, but close. (It returns string- based keys for accessing the objects because my usage has it running in a separate process from the main app.) I reasonably hate the decision I made to return values versus arrays based on number of arguments, and it's not as DRY as it could be, but it's working :) # Example usage # class SituationServer < Phrogz::Librarian # ... # end # # CLEANING_SCHEDULE = 60*60 # Clean up once an hour # MAX_STALENESS = 60*60 # Remove items not used in the last hour # # @server = SituationServer.new # Thread.new{ # loop { # sleep CLEANING_SCHEDULE # removed = @server.remove_stale( MAX_STALENESS ) # puts "Just removed #{removed} stale item#{:s unless removed==1}..." # } # } module Phrogz; end class Phrogz::Librarian def initialize( key_size=4 ) @key_size = key_size @max_keys = 16 ** key_size @serializer = "%0#{key_size}x" @library = {} @stored = {} @access = {} end def store( *objects ) keys = objects.map{ |object| key,obj = @library.find{ |k,o| o==object } unless key # FIXME: bail if @library.size >= MAX_KEYS, or else this will infinite loop # TODO: come up with a better strategy for finding a free key begin key = @serializer % rand( @max_keys ) end while @library.has_key?( key ) @library[ key ] = object @stored[ key ] = Time.now end key } keys.length == 1 ? keys.first : keys end alias_method :<<, :store def fetch( *keys ) now = Time.now # So that multiple accesses are exactly synchronized objects = keys.map{ |key| if @library.has_key?( key ) @access[ key ] = now @library[ key ] end } objects.length == 1 ? objects.first : objects end def size @library.size end def storage_time( *keys ) results = @stored.values_at( *keys ) results.length == 1 ? results.first : results end def age( *keys ) results = @stored.values_at( *keys ).map{ |t| Time.now - t } results.length == 1 ? results.first : results end def last_access( *keys ) results = @access.values_at( *keys ) results.length == 1 ? results.first : results end def staleness( *keys ) results = @access.values_at( *keys ).map{ |t| Time.now - t } results.length == 1 ? results.first : results end def discard( *keys ) results = keys.map{ |key| @stored.delete(key) @access.delete(key) @library.delete(key) } results.length == 1 ? results.first : results end def remove_stale( max_staleness ) now = Time.now stale_keys = @access.select{ |key,time| (now-time) > max_staleness }.keys stale_keys.each{ |key| @stored.delete( key ) @access.delete( key ) object = @library.delete( key ) yield object if block_given? } stale_keys.length end def remove_old( max_age ) now = Time.now stale_keys = @stored.select{ |key,time| (now-time) > max_age }.keys stale_keys.each{ |key| @stored.delete( key ) @access.delete( key ) object = @library.delete( key ) yield object if block_given? } stale_keys.length end end