From: Asher Date: 2010-08-28T00:22:32+09:00 Subject: [ruby-core:31894] Re: Garbage Collection Question --Apple-Mail-7--327332195 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=us-ascii I very much appreciate the response, and this is helpful in describing = the narrative, but it's still a few steps behind my question - but it = may very well have clarified some points that help us get there.=20 Let's stick with the example: a local variable is set as a reference to = an object, the local variable is then set to nil so there is no longer a = live reference to the object. No other ruby space commands have gone on, = so unless Ruby is keeping junk behind the scenes, there should be no = references - not on the Ruby stack, not on the C stack. How does this = object get collected? As shown by the example, it is missed during the = next attempt to GC (as well as any repeated attempts at this point).=20 So what will change to make that object collectable? Are you suggesting = that because it is in Ruby's root node that it gets treated such that it = won't be GC'd until the program terminates?=20 Let's assume this is the case. I should therefore be able to write a = script that creates a non-root object as a child to another object = inside method scope, allow that method to go out of scope, and expect = that the object will be GC'd (as it is neither a root node nor does it = have any live references).=20 This seems to be validated by the following Ruby code: > require 'pp' >=20 > class Hash::Weak < Hash > =20 > def []( key ) >=20 > # get the stored ID - a FixNum, not an object reference to our = weak-referenced object > obj_id =3D super( key.to_sym ) >=20 > # theoretically this should cause non-referenced objects to get = cleaned up=20 > # so long as nothing looks like a pointer or reference to it > ObjectSpace.garbage_collect >=20 > # now get our object from ID > # if it had no references it should have been GC'd and we should = get an=20 > # rb_eRangeError "is not id value" (expected) or "is recycled = object" (possible) > obj =3D ObjectSpace._id2ref( obj_id ) >=20 > return obj >=20 > end > =20 > def []=3D( key, object ) >=20 > # FixNum have a constant ID for value, so can't be copied and = can't be garbage collected > # so object.__id__ cannot be a reference to a child of object and = therefore cannot prevent > # garbage collection on the object > super( key.to_sym, object.__id__ ) >=20 > end > =20 > end >=20 > ################################################## >=20 > # non-rootnode demo >=20 > $weak_hash =3D Hash::Weak.new >=20 > class TestClass > def test_method > child_test_object =3D Object.new > puts 'storing test object' > $weak_hash[ :key ] =3D child_test_object > puts 'hash now contains object id: ' + $weak_hash.pretty_inspect = =20 > end > end > test_object =3D TestClass.new >=20 > test_object.test_method > puts 'id in hash should no longer be valid, as it is out of scope: ' > invalid_key =3D $weak_hash[ :key ] > pp invalid_key Output:=20 > storing test object > hash now contains object id: {:key=3D>2160173880} > id in hash should no longer be valid, as it is out of scope: > = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:21:= in `_id2ref': 0x00000080c1a338 is recycled object (RangeError)=09 > from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:21:= in `[]' > from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:56:= in `
' So that works as expected (so long as GC is run manually; if not run, = object is obviously still valid).=20 So let's try the same thing in this example that we did with the root = node example: > >=20 > # non-rootnode demo with nil-setting >=20 > $weak_hash =3D Hash::Weak.new >=20 > class TestClass > def test_method > child_test_object =3D Object.new > puts 'storing test object' > $weak_hash[ :key ] =3D child_test_object > puts 'hash now contains object id: ' + $weak_hash.pretty_inspect = =20 >=20 > puts 'setting variable referring to test object (ID: ' + = child_test_object.__id__.to_s + ') to nil' > child_test_object =3D nil > puts 'ID for variable referring to test object is now: ' + = child_test_object.__id__.to_s >=20 > print 'getting test object (should fail with rb_eRangeError): ' > invalid_key =3D $weak_hash[ :key ] > pp invalid_key > end > end > test_object =3D TestClass.new >=20 > test_object.test_method > puts 'id in hash should no longer be valid, as it is out of scope: ' > invalid_key =3D $weak_hash[ :key ] > pp invalid_key Output:=20 > storing test object > hash now contains object id: {:key=3D>2160328280} > setting variable referring to test object (ID: 2160328280) to nil > ID for variable referring to test object is now: 4 > getting test object (should fail with rb_eRangeError): > = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:21:= in `_id2ref': 0x00000080c3fe58 is recycled object (RangeError) > =09 > from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:21:= in `[]' > =09 > from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:56:= in `test_method' > =09 > from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:62:= in `
' So that also works as expected.=20 It seems the only time that it does not work as expected, then, is when = the object was instantiated with a reference in the root node.=20 Of course, that is one of the most likely places for people to = instantiate a reference. So can anything be done about this? Are we = simply doomed to wait until program termination for any objects = allocated in the root node to disappear?=20 I intend to look into the patch suggested by brabuhr@gmail.com = (https://sites.google.com/site/brentsrubypatches/), which (so far as = this issue is concerned) appears to amount to: > VALUE *rb_gc_stack_end =3D (VALUE *)STACK_GROW_DIRECTION; > #define rb_gc_wipe_stack() { \ > VALUE *sp =3D alloca(0); \ > VALUE *end =3D rb_gc_stack_end; \ > rb_gc_stack_end =3D sp; \ > __stack_zero(end, sp); \ And some other basic support. I will follow up on that once I have some = time to experiment (particularly sense the patch is intended for 1.8.7 = not 1.9.2). Any particular thoughts on this approach? Presumably there = is some reason it has not been patched to do so?=20 In any case, any thoughts on any of this will be much appreciated. Asher On Aug 26, 2010, at 10:43 PM, Kurt Stephens wrote: > On 8/26/10 11:51 AM, Asher wrote: >> Right - so how does a pointer ever get off the stack? >>=20 > When a C function returns, the C stack pointer register (usually = called "SP") is reset to the frame pointer (sometimes this register is = called "FP"). The FP points to the current function arguments. The = area between the SP and the FP +- the space for arguments (and the other = machine registers) represent the local variables, temporaries and = arguments of the current function call (sometimes called an "activation = record"). >=20 > Load any C program under a debugger and you can see the assembly code. >=20 > The MRI GC knows where "top" (SP) and the bottom of the stack is = because of mostly portable conventions on how C compilers generate code = that manipulate SP and FP and how the operating system lays out the = process' memory. The stack, the machine registers and some global = variables are part of what is sometimes called the "root set". >=20 > The MRI GC scans the root set for values that "look like they point to = Ruby objects" and "marks" those objects recursively as "in use". Any = unmarked objects ("not in use") are definitely not referenced by = anything else and can be deallocated ("sweeped"). The GC must = "stop-the-world" while it does this "marking" and "sweeping" -- nothing = else can happen till this finishes. If the GC couldn't sweep anything, = it allocates more memory from the OS (by calling malloc(), which calls = something at a much lower level (sbrk() or mmap() or something else). >=20 >> For instance, in my example, where the variable with reference to the = object has been assigned nil - the same thing occurs if the variable = goes out of scope. >>=20 >> So in both of those cases, the object "should" be garbage collected; = I understand that it's possible, due to conservative GC, that it might = mistake a number on the stack (a long), etc. as a valid pointer, but = generally when GC runs it should decide that the var (which has no valid = ruby references) is no longer live and should be GC'd. Or am I missing = something? >>=20 >> So we have a var with no references in Ruby that is being marked as = live by the GC because the pointer has not yet been deallocated. So how = does it ever get deallocated in order to not be marked as live? >>=20 >> If what I am seeing is the case (and I assume it cannot be and that I = am missing something) then the object would never be garbage collected. >>=20 >> So how does GC actually occur? >=20 > Collection occurs in MRI when a new object is needed and there are no = unused objects left around and/or there was a certain number of = allocations since the last GC. >=20 >> What causes the pointer to be deallocated? >>=20 > "Pointers" are never allocated or deallocated as in malloc()/free(). = Only objects that have no references to them are deallocated. >=20 > The C compiler generates code that simply increments or decrements the = SP or changes the FP -- Stacks are FIFOs. >=20 > The MRI GC is a very simple "stop-the-world", "mark-and-sweep" = "conservative" collector. Conservative meaning "treat anything that = looks like a pointer to an object as a pointer to an object". This can = cause conservative collectors to keep some objects around longer than = they should. This is also be cause most C compilers leave garbage (old = pointers) on the stack. --Apple-Mail-7--327332195 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=us-ascii
require = 'pp'

class Hash::Weak = < = Hash
  
  de= f []( key )

   =  # get the stored ID - a FixNum, not an object reference to our = weak-referenced object
    obj_id =3D = super( key.to_sym = )

    # = theoretically this should cause non-referenced objects to get cleaned = up 
    # so long as nothing = looks like a pointer or reference to = it
   =  ObjectSpace.garbage_collect

    # now get our object from = ID
    # if it had no references it = should have been GC'd and we should get = an 
    # rb_eRangeError "is = not id value" (expected) or "is recycled object" = (possible)
    obj =3D = ObjectSpace._id2ref( obj_id = )

   =  return = obj

  end
  
  def []=3D( = key, object = )

    # = FixNum have a constant ID for value, so can't be copied and can't be = garbage collected
    # so = object.__id__ cannot be a reference to a child of object and therefore = cannot prevent
    # garbage = collection on the object
    super( = key.to_sym, object.__id__ = )

  end
  
end
##################################################<= /div>

# non-rootnode = demo

$weak_hash =3D = Hash::Weak.new

class = TestClass
  def = test_method
    child_test_object =3D = Object.new
    puts 'storing test = object'
    $weak_hash[ :key ] =3D = child_test_object
    puts 'hash now = contains object id: ' + $weak_hash.pretty_inspect   =  
  end
end
=
test_object =3D = TestClass.new

test_o= bject.test_method
puts 'id in hash should no longer = be valid, as it is out of scope: '
invalid_key =3D = $weak_hash[ :key ]
pp = invalid_key

Outpu= t: 

hash now contains object id: = {:key=3D>2160173880}
id in hash should no longer be = valid, as it is out of scope:
=
from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:21:= in `[]'
from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:56:= in `<main>'

So = that works as expected (so long as GC is run manually; if not run, = object is obviously still valid). 

So = let's try the same thing in this example that we did with the root node = example:

<weak hash code identical to above, so = omitted>

# non-rootnode demo with = nil-setting

$weak_hash =3D = Hash::Weak.new

class = TestClass
  def = test_method
    child_test_object =3D = Object.new
    puts 'storing test = object'
    $weak_hash[ :key ] =3D = child_test_object
    puts 'hash now = contains object id: ' + $weak_hash.pretty_inspect   =  

   =  puts 'setting variable referring to test object (ID: ' + = child_test_object.__id__.to_s + ') to = nil'
    child_test_object =3D = nil
    puts 'ID for variable = referring to test object is now: ' + = child_test_object.__id__.to_s

    print 'getting test object (should fail with = rb_eRangeError): '
    invalid_key =3D= $weak_hash[ :key ]
    pp = invalid_key
  end
end<= /div>
test_object =3D = TestClass.new

test_o= bject.test_method
puts 'id in hash should no longer = be valid, as it is out of scope: '
invalid_key =3D = $weak_hash[ :key ]
pp = invalid_key

Output:&nb= sp;

storing test object
setting variable referring to = test object (ID: 2160328280) to nil
ID = for variable referring to test object is now: 4
from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:21:= in `[]'
from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:56:= in `test_method'
from = /Users/ahaig/Projects/rp/ruby/weakhash/projects/RPWeakHash/weakhash.rb:62:= in `<main>'

So = that also works as expected. 

It seems the = only time that it does not work as expected, then, is when the object = was instantiated with a reference in the root = node. 

Of course, that is one of the most = likely places for people to instantiate a reference. So can anything be = done about this? Are we simply doomed to wait until program termination = for any objects allocated in the root node to = disappear? 

I intend to look into the = patch suggested by brabuhr@gmail.com (https://sites.go= ogle.com/site/brentsrubypatches/), which (so far as this issue is = concerned) appears to amount to:

VALUE *rb_gc_stack_end =3D (VALUE = *)STACK_GROW_DIRECTION;
#define = rb_gc_wipe_stack() {   = \
  VALUE *sp =3D = alloca(0);         = \
  VALUE *end =3D = rb_gc_stack_end; =  \
  rb_gc_stack_end =3D = sp;           = \
  __stack_zero(end, = sp);   = = \

And some other basic support. I will follow up on that = once I have some time to experiment (particularly sense the patch is = intended for 1.8.7 not 1.9.2). Any particular thoughts on this approach? = Presumably there is some reason it has not been patched to do = so? 

In any case, any thoughts on any of = this will be much = appreciated.

Asher

= On Aug 26, 2010, at 10:43 PM, Kurt Stephens wrote:

On = 8/26/10 11:51 AM, Asher wrote:
Right - so = how does a pointer ever get off the stack?

When a C function returns, the C stack = pointer register (usually called "SP") is reset to the frame pointer = (sometimes this register is called "FP").  The FP points to the = current function arguments.  The area between the SP and the FP +- = the space for arguments (and the other machine registers) represent the = local variables, temporaries and arguments of the current function call = (sometimes called an "activation record").

Load any C program = under a debugger and you can see the assembly code.

The MRI GC = knows where "top" (SP) and the bottom of the stack is because of mostly = portable conventions on how C compilers generate code that manipulate SP = and FP and how the operating system lays out the process' memory. =  The stack, the machine registers and some global variables are = part of what is sometimes called the "root set".

The MRI GC scans = the root set for values that "look like they point to Ruby objects" and = "marks" those objects recursively as "in use".  Any unmarked = objects ("not in use") are definitely not referenced by anything else = and can be deallocated ("sweeped").  The GC must "stop-the-world" = while it does this "marking" and "sweeping" -- nothing else can happen = till this finishes.   If the GC couldn't sweep anything, it = allocates more memory from the OS (by calling malloc(), which calls = something at a much lower level (sbrk() or mmap() or something = else).

For instance, in my example, = where the variable with reference to the object has been assigned nil - = the same thing occurs if the variable goes out of = scope.

So in both of = those cases, the object "should" be garbage collected; I understand that = it's possible, due to conservative GC, that it might mistake a number on = the stack (a long), etc. as a valid pointer, but generally when GC runs = it should decide that the var (which has no valid ruby references) is no = longer live and should be GC'd. Or am I missing = something?

So we have a = var with no references in Ruby that is being marked as live by the GC = because the pointer has not yet been deallocated. So how does it ever = get deallocated in order to not be marked as = live?

If what I am = seeing is the case (and I assume it cannot be and that I am missing = something) then the object would never be garbage = collected.

So how does GC = actually occur?

Collection occurs in MRI when a new = object is needed and there are no unused objects left around and/or = there was a certain number of allocations since the last = GC.

What causes the pointer to be = deallocated?

"Pointers" are never allocated or = deallocated as in malloc()/free(). Only objects that have no references = to them are deallocated.

The C compiler generates code that = simply increments or decrements the SP or changes the FP -- Stacks are = FIFOs.

The MRI GC is a very simple "stop-the-world", = "mark-and-sweep" "conservative" collector.  Conservative meaning = "treat anything that looks like a pointer to an object as a pointer to = an object".  This can cause conservative collectors to keep some = objects around longer than they should.  This is also be cause most = C compilers leave garbage (old pointers) on the = stack.

<snip>
<= br>
= --Apple-Mail-7--327332195--