user1939480
user1939480

Reputation: 33

Modify Ruby array in C with RubyInline

I have arrays in Ruby and I would like to extend them with .normalize method. This method should modify array such that all it`s elements sum up to 1. This is way too expensive in Ruby, so I want to do it in C with RubyInline.

require "rubygems"
require "inline"

class Array
inline do |builder|
    builder.c_raw '
     static VALUE normalize(VALUE self) {
        double total_size = 0, len;
        int i;

        VALUE* array = RARRAY_PTR(self);
        len = RARRAY_LEN(self);

        for(i=0; i < len; i++){
            total_size += NUM2DBL(array[i]);
        }

        for(i=0; i < len; i++){
            array[i] = INT2NUM(NUM2DBL(array[i])/total_size);
        }

        return array;
    }'
  end
end

a = [1,2,0,0,0,0,0,3,0,4]

puts a.normalize.inspect

This results in

$ ruby tmp.rb 
tmp.rb:29: [BUG] Segmentation fault
ruby 1.8.7 (2011-06-30 patchlevel 352) [x86_64-linux]

Aborted (core dumped)

EDIT: after some debugging, crash seems to come at

VALUE* array = RARRAY_PTR(self);

Upvotes: 3

Views: 899

Answers (1)

Frederick Cheung
Frederick Cheung

Reputation: 84114

There are a few things to fix here:

When you use c_raw rubyinline doesn't try to detect arity, and instead assumes that you want to use a variable number of arguments. You can either override this (pass :arity => 0) or change your method signature to

VALUE normalize(int argc, VALUE *argv, VALUE self)

At the moment rubyinline is assuming your method has that signature, so you're probably reinterpreting the integer 0 as a pointer.

Next up, at the moment you're always filling the arrays with zero, because all the array elements are < 1, and then you're converting to an integer so you get 0 - use rb_float_new to turn a double back into a ruby Float.

Lastly, your return value is wrong, it's a VALUE * instead of a VALUE. You probably want to return self instead.

Finally it would be more ruby-like to call your method normalize!. By default ruby inline extracts the method name from the c function name, which of course doesn't let you use exclamation marks like that. You can override that with the method_name option.

Alltogether, my version of your example looks like

require "rubygems"
require "inline"

class Array
  inline do |builder|
    builder.c_raw <<-'SRC', :method_name => 'normalize!', :arity => 0
     static VALUE normalize(VALUE self) {
        double total_size = 0;
        size_t len, i;

        VALUE* array = RARRAY_PTR(self);
        len = RARRAY_LEN(self);

        for(i=0; i < len; i++){
            total_size += NUM2DBL(array[i]);
        }
        for(i=0; i < len; i++){
            array[i] = rb_float_new((NUM2DBL(array[i])/total_size));
        }

        return self;
    }
    SRC
  end
end

a = [1,2,0,0,0,0,0,1,0,4]

puts a.normalize!.inspect

Upvotes: 3

Related Questions