(This is a translation of ko1's blog. All mistakes are mine.)
This is the 8th day of the Ruby VM Advent Calendar.
I'm slowly running out of breath.
Today is,
- Use of
ObjectSpace.reachable_objects_from()
- GC Advent Calendar - Continued use of
ObjectSpace.reachable_objects_from()
- GC Advent Calendar
So let's try to take a look at the already introduced
ObjectSpace.reachable_objects_from(obj)
. Currently, if you don't require
objspace
as require 'objspace'
, you can't use use the library.
ObjectSpace.reachable_objects_from(obj)
lets you follow the direct objects
from obj
, and returns an enumerable.
Let's look at the following examples:
- When obj is ["a", "b", "c"], returns [Array, "a", "b", "c"]
- When obj is ["a", "a"], returns [Array, "a", "a"]
- When obj is [a = "a", a], returns [Array, "a"]
I don't think 1 is a problem, but do you understand the difference between 2 and 3? When it follows multiple identical objects, it only returns one of them.
Well, if you use this, then you can follow objects from obj
. Lets try writing code that
finds the memory size for all objects. Be careful! This isn't just
"following direct", objects, but "following all objects". That is to say,
we'll recursively apply ObjectSpace.reachable_objects_from(obj)
.
For example, an arrangement like this: [['a' * 100, 'b' * 100], 'c' * 100]
will follow three 100 character objects, so it means we want the sum to be 300
bytes (strictly speaking, it will also follow an Array, but this calculation is
difficult, so we will exclude it).
For getting the object consumption size, use the ObjectSpace.memsize_of(obj)
method. This is in to 1.9.
First off, here is the code:
require 'objspace'
require 'pp'
def memsize_of_all_reachable_objects_from(obj, exclude_class = Module)
objs = {}
queue = [obj]
while obj = queue.pop
next if objs[obj.object_id]
next unless reachable_objects = ObjectSpace.reachable_objects_from(obj)
reachable_objects.each{|o|
case o
when ObjectSpace::InternalObjectWrapper
next if objs[o.internal_object_id]
else
next if objs[o.object_id]
end
queue.push o if !exclude_class || !o.kind_of?(exclude_class)
}
objs[obj.respond_to?(:internal_object_id) ? obj.internal_object_id : obj.object_id] = obj
end
objs.inject(0){|r, (_, o)| r += ObjectSpace.memsize_of(o)}
end
memsize_of_all_reachable_objects_from(obj, klass=Module)
returns the size of
all of the objects reachable from obj
. But, the class object specified in
the second parameter klass
is excluded. The default is the Module
class
object. That is to say, we can try not following things like Array
. If you
pass nil
, then it will exclude nothing.
Anyway, let's try it out!
p memsize_of_all_reachable_objects_from([['a' * 100, 'b' * 100], 'c' * 100])
#=> 300
It correctly returned 300.
Let's also try including the size of the Array.
p memsize_of_all_reachable_objects_from([['a' * 100, 'b' * 100], 'c' * 100], nil)
#=> 639428
A large number came back. There are a large number of objects reachable from
the Array to climb, and it seems the size is also very large (On the way, if
you look at objs
entry count, you'll understand, but it seems the total
number of reachable objects is 5604. However, there is a chance it depends on
your environment).
memsize_of_all_reachable_objects_from()
is a comparatively easy method, but
there is an important warning for when you're using
ObjectSpace::InternalObjectWrapper
. I'll introduce this the next chance I
get (as a connected article). If you're interested, please read
ext/objspace/objspace.c
.
Anyway, that's it for today.
This was code written RubyConf Taiwan 2012.
Materials: http://www.atdot.net/~ko1/activities/rubyconf.tw2012_ko1.pdf
In the materials, there is a map with explanation.