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