On Jan 22, 2006, at 8:54 AM, Ilmari Heikkinen wrote:

> On 1/22/06, Ilmari Heikkinen <ilmari.heikkinen / gmail.com> wrote:
>> pretty much ground my framerate to ground by requiring a 0.2s GC run
>
> Argh, sorry, magnitude error. The correct GC run time is 0.02s. Not so
> bad, but still a 60fps -> 20fps glitch.

In a completely separate world (Lua code running under a scene graph  
written in C++; no Ruby anywhere) I recently hit a place where I  
thought I needed to allocate ~200 Vector objects per frame.

(I was using a recursive function to calculate 3D bezier curves[1],  
which needed to allocate and preserve 4 new Vector objects each call.)

It was causing noticeable lurching when the GC kicked in  
occasionally. I found two interesting things:

1) Running Lua's GC manually *every frame* resulted in better memory  
performance and faster overall framerate than running it every 2 or  
10 or 50 or 100 frames. My only (lame) guess was that waiting longer  
yielded a larger memory pool too wade through when looking for items  
to GC. (?)

2) Because I really didn't need to preserve the 200 Vectors from  
frame to frame (the final results of the recursive calculation were  
copied into the position vectors for points on the line), I was able  
to remove the per-frame memory allocations altogether by abstracting  
the Vector allocation into a pooled-vector manager. Doing this gave  
me far-better frame rates than I was getting with the GC-every-frame  
approach.

This isn't applicable specifically to Ruby, but applicable generally:  
when you can't remove memory allocations, see if you can at least re- 
use them.

In an attempt to make this post Ruby-specific, I give you a pooled  
object manager that I've just written, based on the Lua version I  
wrote at work. You create a pool by specifying an object that is the  
template/factory, and a method to call on that object (defaults to  
'new'). Every time you ask for an object, it will hand you one from  
the pool, or create a new instance. The #reset method makes all items  
in the pool re-usable again (i.e. call at the start of a new frame).


class ObjectPool
   def initialize( template, method=:new, template_in_pool=false )
     @template = template
     @method = method
     @pool = [ ]
     if template_in_pool
       @pool << template
     end
     reset
   end

   # Make all items in the pool available again
   def reset
     @next_available = 0
   end

   # Remove references to all items not currently in use
   def drain
     @pool[ @next_available..-1 ] = nil
   end

   # Return a new item from the pool, creating a new one if needed
   def next
     unless item = @pool[ @next_available ]
       @pool << ( item = @template.send( @method ) )
     end
     @next_available = @next_available + 1
     item
   end

   def inspect
     "<ObjectPool of #{@pool.size} #{@template}>"
   end
end

class Vector
   attr_accessor :x, :y, :z
   def initialize( x=0, y=0, z=0 ) @x, @y, @z = x,y,z end
   def clone() self.class.new( @x, @y, @z ) end
   def inspect() "<Vector:0x#{object_id.to_s(16)} #@x,#@y,#@z>" end
end

##################################################################
# Showing how to create a pool of class instances
##################################################################
pool = ObjectPool.new( Vector )
p pool
#=> <ObjectPool of 0 Vector>

3.times{ |i|
   v = pool.next
   v.x = v.y = v.z = i
   p v
}
#=> <Vector:0x195518 0,0,0>
#=> <Vector:0x195392 1,1,1>
#=> <Vector:0x19520c 2,2,2>

p pool
#=> <ObjectPool of 3 Vector>

pool.reset
3.times{ p pool.next }
#=> <Vector:0x195518 0,0,0>
#=> <Vector:0x195392 1,1,1>
#=> <Vector:0x19520c 2,2,2>

p pool
#=> <ObjectPool of 3 Vector>


##################################################################
# Showing how to create a pool based off of a template object
##################################################################
v = Vector.new( 1, 2, 3 )
pool2 = ObjectPool.new( v, :clone, true )
p pool2
#=> <ObjectPool of 1 #<Vector:0x32ad64>>

3.times{ p pool2.next }
#=> <Vector:0x1956b2 1,2,3>
#=> <Vector:0x194672 1,2,3>
#=> <Vector:0x1944ec 1,2,3>

pool2.reset
3.times{ p pool2.next }
#=> <Vector:0x1956b2 1,2,3>
#=> <Vector:0x194672 1,2,3>
#=> <Vector:0x1944ec 1,2,3>

p pool2
#=> <ObjectPool of 3 #<Vector:0x32ad64>>



[1] http://www.antigrain.com/research/adaptive_bezier/index.html#toc0003