Subject:
Re: [ruby-ffi] Proper memory management
From:
Wayne Meissner
Date:
5/5/10 9:19 PM
To:
ruby-ffi@googlegroups.com

Use FFI::AutoPointer - its there for exactly this reason (having one
implementation of the whole "clean up native memory when ruby object
instance is finalized" is better than having many, slightly broken
variants).

FFI::AutoPointer also has the advantage on JRuby of _not_ using
ObjectSpace, so a) it will actually work in jruby-1.5.0 and later
where objectspace is not on by default, and b) is less overhead, since
its implemented via standard java PhantomReferences.

Use it something like this:

class VectorPointer < FFI::AutoPointer

  def self.release(ptr)
   # ptr is the original pointer you passed in to create VectorPointer
    GSLng.backend.gsl_free_vector(ptr)
  end
end

Then use it like thus:
 ptr = VectorPointer.new(GSLng.backend.gsl_alloc_vector(n))

It is a FFI::Pointer subclass, so you can pass it wherever a :pointer
parameter is needed, or use it in a Struct :pointer field, etc.

There is also AutoPointer#free() to free the memory earlier than the
GC cleanup (which is a good idea when you can use it), #autorelease=()
to toggle on/off the auto cleanup.


On 6 May 2010 08:56, v01d <phreakuencies@gmail.com> wrote:
> Hi,
> I'm in the process of developing a Ruby binding to the GSL numerical
> library using FFI. Recently I stumbled upon a problematic issue
> regarding memory management and finalizers.
>
> Since the GSL library allows creation and destruction of instances
> like this:
> gsl_vector* ptr = gsl_alloc_vector(size_t n);
> ...
> gsl_free_vector(ptr);
>
> Then, in Ruby I do something like this:
>
> class Vector
>  def initialize(n)
>    @ptr = GSLng.backend.gsl_alloc_vector(n)
>    Vector.define_finalizer(self, @ptr)
>  end
>
>  def Vector.define_finalizer(self, ptr)
>    ObjectSpace.define_finalizer(self, lambda {|id|
> GSLng.backend.gsl_free_vector(ptr)})
>  end
> end
>
> The problem is that if later I instatiante a Vector inside a loop,
> like:
> 10000.times do
>  Vector.new(3)
> end
>
> This obviously eats a lot of memory until GC runs. Now, if I do:
> 10000.times do
>  Vector.new(3)
>  GC.start
> end
>
> the memory that the Vector instances themselves occupy is freed but
> the finalizer is not called (it is later called at program end),
> therefore all of the memory allocated through gsl_alloc_vector() is
> sitting there until program ends.
>
> I know that Ruby doesn't guarantee that finalizers are called in any
> particular moment (even after GC.start, which I thought would
> suffice). So what I'm asking probably is not really related to FFI
> itself, but I wanted to ask here since I imagined this would be a
> common pattern among FFI users. The question is then: is there a
> better way to manage this type of memory? I obviously cant use the
> transaction-like pattern (like File.open) since for a Vector that
> wouldn't make sense. It would be ideal to make the gsl_vector_free
> call when the actual Vector class is free'd. Maybe I need to go to a
> lower level for this and use the C api to register this call in the
> "free" function for this class, but that would be overkill since I
> wanted to avoid using C altogether.
>
> Thank you!
>