The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
WeakHash, WeakRefs and lambda evilness

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Eigen Class

Posts: 358
Nickname: eigenclass
Registered: Oct, 2005

Eigenclass is a hardcore Ruby blog.
WeakHash, WeakRefs and lambda evilness Posted: Jan 24, 2006 1:09 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Eigen Class.
Original Post: WeakHash, WeakRefs and lambda evilness
Feed Title: Eigenclass
Feed URL: http://feeds.feedburner.com/eigenclass
Feed Description: Ruby stuff --- trying to stay away from triviality.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Eigen Class
Latest Posts From Eigenclass

Advertisement

James Edward Gray II asked in ruby-talk:176543 for

a Hash-like structure, using WeakRef, so that the key value pairs can be garbage collected as needed. I only need to support [] () and []=(), not the whole range of Hash functions. If the pair has been collected, I just need a simple nil returned.

It was 1am so instead of writing a full solution, I just pointed out some closure evilness in his WeakCache. He had written a WeakCache where only values were "weak" (i.e. could be reclaimed by the GC at any time), and was using a finalizer to remove the corresponding key from the @cache hash:

   def []=( key, value )
      ObjectSpace.define_finalizer(value, lambda { @cache.delete(key) })

That closure captures the whole environment, so the value would never be GCed: the finalizer itself would point to it!

In the derived WeakCache I posted, I forgot to consider that several keys could refer to the same value, as reminded by Jeffrey Schwab in ruby-talk:176720. So the code would now look like (ruby-talk:176778)

# Exercise left to the reader: add thread-safety
class WeakCache
  attr_reader :cache
  def initialize( cache = Hash.new )
    @cache = cache
    @rev_cache = Hash.new{|h,k| h[k] = {}}
    @reclaim_lambda = lambda do |value_id| 
      if @rev_cache.has_key? value_id
        @rev_cache[value_id].each_key{|key| @cache.delete key}
        @rev_cache.delete value_id
      end
    end
  end

  def []( key )
    value_id = @cache[key]
    return ObjectSpace._id2ref(value_id) unless value_id.nil?
    nil
  end

  def []=( key, value )
    @rev_cache[value.object_id][key] = true
    @cache[key] = value.object_id
    ObjectSpace.define_finalizer(value, @reclaim_lambda)
  end
end

Thinking about the semantics and improvements towards a better WeakHash

In that WeakCache, only values are weak: keys will not perish spontaneously. Robert Klemme presented the following code, where both the keys and the values could be GCed:

RKWeakHash = DelegateClass(Hash) 
class RKWeakHash
  def []=(key,val) # !> method redefined; discarding old []=
    __getobj__[WeakRef.new(key)] = WeakRef.new(val)
  end
  
  def [](key) # !> method redefined; discarding old []
    __getobj__[WeakRef.new(key)]
  end

  def each(&b) # !> method redefined; discarding old each
    __getobj__.each do |k,v|
      b[k.__getobj__, v.__getobj__] unless k.__getobj__.nil?
    end
    self
  end
  
  def cleanup
    delete_if {|k,v| k.__getobj__.nil?}
  end
end

I'm normally wary of things like DelegateClass, Singleton, WeakRef, Tempfile... because it's so easy to run into horrible performance*1 issues. I anticipated that Robert Klemme's solution would be very slow, and wrote a SimpleWeakHash with relaxed semantics:

  • the key->value associations can disappear at any time
  • all existent associations are wiped out when the GC kicks in

In particular, it removes the tacit requirement that an association remain alive as long as there is an external reference (i.e. outside the hash) to either the key or the value. The two advantages we get in exchange are:

  • increased speed (we'll see how much later)
  • no special code to keep SimpleWeakHash invariants: we can expose all the methods from Hash, unlike WeakCache

class SimpleWeakHash
  def initialize
    set_internal_hash
  end
  
  def [](key)
    __get_hash__[key]
  end

  def []=(key, value)
    __get_hash__[key] = value
  end

  def method_missing(meth, *args, &block)
    __get_hash__.send(meth, *args, &block)
  end

  private
  def __get_hash__
    old_critical = Thread.critical
    Thread.critical = true
    @valid or set_internal_hash
    return ObjectSpace._id2ref(@hash_id)
  ensure
    Thread.critical = old_critical
  end

  def set_internal_hash
    hash = {}
    @hash_id = hash.object_id
    @valid = true
    ObjectSpace.define_finalizer(hash, lambda{@valid = false})
    hash = nil
  end
end

Note that the #[] and #[]= are redundant, but anyway they're so short...

Implementing WeakHash


Read more...

Read: WeakHash, WeakRefs and lambda evilness

Topic: Writing massively scalable software Previous Topic   Next Topic Topic: Dominance

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use