WeakRef Hash

  • Thread starter James Edward Gray II
  • Start date
J

James Edward Gray II

I need 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.

I've tried implementing this a couple of different ways, but keep
running into trouble. Does anyone have any tips?

James Edward Gray II
 
R

Robert Klemme

James said:
I need 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.

I've tried implementing this a couple of different ways, but keep
running into trouble. Does anyone have any tips?

Can't you just do something like this?

require 'delegate'
require 'weakref'
WeakHash = DelegateClass(Hash)
class WeakHash
def []=(key,val)
__getobj__[WeakRef.new(key)] = WeakRef.new(val)
end

def [](key)
__getobj__[WeakRef.new(key)]
end

def each(&b)
__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

Kind regards

robert
 
J

James Edward Gray II

I need 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.

I've tried implementing this a couple of different ways, but keep
running into trouble. Does anyone have any tips?

The following code, adapted from an old post by Guy Decoux seems to
do the trick:

class WeakCache
def initialize( cache = Hash.new )
@cache = cache
end

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

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

__END__

I'm still interested in seeing a WeakRefHash though, if anyone has
rolled something similar in the past...

James Edward Gray II
 
M

Mauricio Fernandez

The following code, adapted from an old post by Guy Decoux seems to
do the trick:

class WeakCache
def initialize( cache = Hash.new )
@cache = cache
end [...]
def []=( key, value )
ObjectSpace.define_finalizer(value, lambda { @cache.delete(key) })
==============================
This keeps a reference to value so it will never be reclaimed:

class WeakCache
attr_reader :cache
def initialize( cache = Hash.new )
@cache = cache
end

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

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

RUBY_VERSION # => "1.8.4"
RUBY_RELEASE_DATE # => "2005-12-24"
c = WeakCache.new
puts c.cache.size
100000.times{|i| c = i.to_s}
GC.start
puts c.cache.size
__END__
# >> 0
# >> 100000


Compare to this:


class WeakCache
attr_reader :cache
def initialize( cache = Hash.new )
@cache = cache
end

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

def make_lambda(key)
lambda{|value| @cache.delete(key) }
end

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

RUBY_VERSION # => "1.8.4"
RUBY_RELEASE_DATE # => "2005-12-24"
c = WeakCache.new
puts c.cache.size
100000.times{|i| c = i.to_s}
GC.start
puts c.cache.size
__END__
# >> 0
# >> 3



That is very inefficient though, you want something more like


class WeakCache
attr_reader :cache
def initialize( cache = Hash.new )
@cache = cache
@rev_cache = {}
@reclaim_method = method:)reclaim_value)
end

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

def reclaim_value(value_id)
@cache.delete @rev_cache[value_id]
@rev_cache.delete value_id
end

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

RUBY_VERSION # => "1.8.4"
RUBY_RELEASE_DATE # => "2005-12-24"
c = WeakCache.new
puts c.cache.size
100000.times{|i| c = i.to_s}
GC.start
puts c.cache.size
__END__
# >> 0
# >> 4
 
J

James Edward Gray II

The following code, adapted from an old post by Guy Decoux seems to
do the trick:

class WeakCache
def initialize( cache = Hash.new )
@cache = cache
end [...]
def []=( key, value )
ObjectSpace.define_finalizer(value, lambda { @cache.delete
(key) })

==============================
This keeps a reference to value so it will never be reclaimed:

[snip sensational examples]

Thank you for the excellent lesson!

James Edward Gray II
 
R

Robert Klemme

2006/1/24 said:
Can't you just do something like this?

require 'delegate'
require 'weakref'
WeakHash =3D DelegateClass(Hash)
class WeakHash
def []=3D(key,val)
__getobj__[WeakRef.new(key)] =3D WeakRef.new(val)
end

def [](key)
__getobj__[WeakRef.new(key)]
end

def each(&b)
__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 didn't see this message while the gateway was down. Sorry about that.

I can't use this code as is. I assume you didn't set up delegation
quite right:

What problem exactly do you have with it?
class WeakHash < DelegateClass(Hash)
def initialize
super(Hash.new)
end

# ...
end

No need to inherit to fix this. You can simply do

require 'delegate'
Wh=3DDelegateClass(Hash)
class Wh
def initialize(h=3D{})
__setobj__ h
end
end
Some questions the above raises for me:

1. What will Ruby do if a key is GCed, but not a value?

This was just rough outline code. For a production version I'd
probably change it to consider a pair as gone if either of them is
GC'ed.
2. How does this code deal with a value being GCed when the key
remains?

It will yield nil - but see 1.
3. Don't we need to shut off GC in places, to keep references from
disappearing before we can use them?

No. Because while the yield takes place instances are hard referenced
and cannot be gc'ed.
Thanks for the help.

Ywc

Kind regards

robert
 
R

Robert Klemme

2006/1/24 said:
When I placed the code you posted in a file and tried to create
WeahHash, the program crashed. (Wrong number of arguments to
initialize().) I assume it would have worked if I passed the Hash
manually though.
Yep.


Here's another question for you: What are we gaining by delegating
to an object here, as opposed to subclassing?

Note, the difference between yours and mine was that you first
delegated and then subclassed the delegating class. I'd say do either
of both in this case but not both. Generally I'd prefer delegation in
this use case because we really have a case of wrapping and unwrapping
during insertion and retrieval ops. IMHO inheritance is often used in
many cases where it's not appropriate (although in this case you could
argue that a WeakHash is really a Hash - but then again, it doesn't
share some of Hash's basic properties, namely that the contents do not
change suddenly). There's no hard written rule to be found here.
Good answers all around. Thanks.

In fact to be really sure, one should probably do something like this:

def each
__getobj__.each do |wk,wv|
k,v=3Dwk.__getobj__, wv.__getobj__
yield unless k.nil? || v.nil?
end
end

Cheers

robert
 
M

Mauricio Fernandez

I need 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.

I've tried implementing this a couple of different ways, but keep
running into trouble. Does anyone have any tips?

I summarized the solutions seen in this thread so far, and presented a couple
new ones at
http://eigenclass.org/hiki.rb?weakhash+and+weakref

There are other ways to implement WeakHash, I'll probably add some to the
above page (later).

HTH
 
R

Robert Klemme

2006/1/24 said:
I summarized the solutions seen in this thread so far, and presented a co= uple
new ones at
http://eigenclass.org/hiki.rb?weakhash+and+weakref

There are other ways to implement WeakHash, I'll probably add some to the
above page (later).

Nice work. Btw, it's expected that my lookup code is slow because it
has to create a weakref for every insertion. We could try to optimize
this by providing a single weakref for the hash that is used for
lookups.

class WR < WeakRef
attr_accessor :__getobj__
end

class RKWeakHash
def initialize(...)
@lookup =3D WR.new nil
end

def [](x)
@lookup.__getobj__ =3D x
__getobj__[@lookup]
end
end

Unfortunately this is not thread safe, unless one invests more and
stores lookup thread local...

robert
 
J

James Edward Gray II

Note, the difference between yours and mine was that you first
delegated and then subclassed the delegating class.

My usage was straight out of the delegate docs:

http://www.ruby-doc.org/stdlib/libdoc/delegate/rdoc/index.html

Technically, I wrote those docs, but I certainly didn't invent that
idiom. I mainly copied it from Matz's usage in TempFile. (I believe
he wrote that, my apologies if I mis-credited there.)

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,077
Latest member
SangMoor21

Latest Threads

Top