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?
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
···
On Jan 23, 2006, at 10:17 AM, James Edward Gray II wrote:
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 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
There are other ways to implement WeakHash, I'll probably add some to the
above page (later).
HTH
···
On Tue, Jan 24, 2006 at 01:17:07AM +0900, James Edward Gray II wrote:
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 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:
class WeakHash < DelegateClass(Hash)
def initialize
super(Hash.new)
end
# ...
end
Some questions the above raises for me:
1. What will Ruby do if a key is GCed, but not a value?
2. How does this code deal with a value being GCed when the key remains?
3. Don't we need to shut off GC in places, to keep references from disappearing before we can use them?
Thanks for the help.
James Edward Gray II
···
On Jan 24, 2006, at 9:51 AM, Robert Klemme wrote:
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
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 = WR.new nil
end
def (x) @lookup.__getobj__ = x
__getobj__[@lookup]
end
end
Unfortunately this is not thread safe, unless one invests more and
stores lookup thread local...
I like your technique, but suppose the same value is stored for more than one key in a WeakCache? When the value is finalized, only the last key will be removed from the cache. I think it might be better to let the reverse cache hold more than one key per value.
I like your technique, but suppose the same value is stored for more
than one key in a WeakCache? When the value is finalized, only the last
key will be removed from the cache. I think it might be better to let
the reverse cache hold more than one key per value.
You're very right, I should have written something like
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
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?
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.
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=DelegateClass(Hash)
class Wh
def initialize(h={})
__setobj__ h
end
end
Here's another question for you: What are we gaining by delegating to an object here, as opposed to subclassing?
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.
>> 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?
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.
>> 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=DelegateClass(Hash)
> class Wh
> def initialize(h={})
> __setobj__ h
> end
> end
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.
>> 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.
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=wk.__getobj__, wv.__getobj__
yield unless k.nil? || v.nil?
end
end
Cheers
robert
···
2006/1/24, James Edward Gray II <james@grayproductions.net>:
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
···
On Jan 24, 2006, at 2:47 PM, Robert Klemme wrote:
Note, the difference between yours and mine was that you first
delegated and then subclassed the delegating class.