Subject:
[ruby-ffi] Re: Proper memory management
From:
v01d
Date:
5/5/10 8:18 PM
To:
ruby-ffi

Continuing brainstorming...

I was thinking on a solution that involves a little coding in C but
which I think could be a good candidate to do with some FFI magic
(maybe extending Ruby::FFI somehow).

My idea is to store not only @ptr, but a custom class that wraps the
pointer (using Data_Wrap_Struct) and registers a "free function" that
will call gsl_vector_free. This instance is saved as @ptr_holder so
that when the Vector instance is GC'd, @ptr_holder is also GC'd and
gsl_vector_free is called without having to wait for the finalizer to
be executed.

Now the problem is that requires a strange mix of C API usage and FFI.
First I would have to define a function like this on the C side:
VALUE hold_vector_ptr(VALUE ptr) {
  gsl_vector* ptr2 = (gsl_vector*)NUM2ULL(ptr);
  return Data_Wrap_Struct(cSomeClass, NULL, gsl_vector_free, ptr2);
}

so that I can do:
  def initialize(n)
    @ptr = GSLng.backend.gsl_alloc_vector(n)
    @ptr = GSLng.hold_vector_ptr(@ptr.address)
    Vector.define_finalizer(self, @ptr)
  end

What would be ideal in this case is to have a way to avoid the need of
defining at the C side one of these hold_*_ptr functions for each
pointer type (there are a lot under GSL). What I mean is something
like doing:
hold_ptr(@ptr.address, :gsl_vector_free)
on the Ruby side. At the C side this would require grabbing the
appropriate C function (in this case gsl_vector_free()) using the C
FFI API or something).

Ideas welcome (not necessarily for this proposed "solution", but for
any other that serves my purpose).

On May 5, 7:56 pm, v01d <phreakuenc...@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!