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!