Search code examples
carraysrubyruby-c-extension

Ruby C API - From ruby array to C array


I am passing an array (matrix) from Ruby to a C function. At the moment I am using the following code

VALUE matmat_mul(VALUE self, VALUE matrixA, VALUE matrixB)
{
    int rowsA = RARRAY_LEN(matrixA);
    VALUE firstElement = rb_ary_entry(matrixA, 0);
    int colsA = RARRAY_LEN(firstElement);
    int rowsB = RARRAY_LEN(matrixB);
    firstElement = rb_ary_entry(matrixB, 0);
    int colsB = RARRAY_LEN(firstElement);

    int i,j;
    double *matA = (double *)malloc(rowsA * colsA * sizeof(double));
    double *matB = (double *)malloc(rowsB * colsB * sizeof(double));

    VALUE rowA;
    for (i=0; i<rowsA; i++)
    {
        rowA = rb_ary_entry(matrixA, i);
        for (j=0; j<colsA; j++)
        {
            matA[i * colsA + j] = NUM2DBL(rb_ary_entry( rowA, j));
        }
    }

    //  same for matrix B
    ....
    ....

    // Perform operation C = A x B

    VALUE matrixC = rb_ary_new2(rowsC);
    VALUE rowC;
     for (i=0; i<rowsC; i++) {
         rowC = rb_ary_new2(colsC);
         for (j=0; j<colsC; j++) {
              rb_ary_store(rowC, j, DBL2NUM(matC[i * colsC + j]));
         }
         rb_ary_store(matrixC, i, rowC);
     }


return matrixC
}

Is there a better/quicker way to convert a Ruby array to a C array and viceversa?


Solution

  • No there is not a quicker way to convert Ruby Array to a C structure. That's because the Ruby Array could contain a mixture of any other kind of Ruby object, many of which could not be converted to a C double

    There is another option though - NArray. This is a very efficient way of dealing with numerical multi-dimensional arrays in Ruby. There is a lot less procedure converting from an NArray to C, but it is entirely different way of doing things.

    Some of it is a little complex. In summary . . .

    Load the narray.h library in extconf.rb

    Original version of this was from fftw3 gem (I have simplified a little):

    require "mkmf"
    require "narray"
    
    narray_dir = File.dirname(Gem.find_files("narray.h").first) rescue $sitearchdir
    dir_config('narray', narray_dir, narray_dir)
    
    if ( ! ( have_header("narray.h") && have_header("narray_config.h") ) )
       puts "Header narray.h or narray_config.h is not found."
       exit(-1)
    end
    
    create_makefile( 'my_lib_name/my_lib_name' )
    

    Cast input NArray objects to the data type you want to work with

    Here's an example instance method that can access the NArray

    VALUE example_narray_param( VALUE self, VALUE rv_narray ) {
      // Cast the input to the data type you want - here 32-bit ints
      volatile VALUE new_narray = na_cast_object(rv_narray, NA_LINT);
    
      // NARRAY is the C struct interface to NArray data
      struct NARRAY *na_items;
    
      // This macro is NArray's equivalent of NUM2DBL, pointing na_items 
      // at the data
      GetNArray( new_narray, na_items );
    
      // row now points natively to the data
      int * row = (int*) na_items->ptr;
    

    For multi-dimensional arrays like your matrix, NArray uses a single pointer with multiplier offsets, similar to your matA[i * colsA + j] - going into full detail on this would be too long, but hopefully this is enough of a start to help you decide if this is the right solution for you.

    I actually use this approach a lot in some personal projects. They are MIT licensed, so feel free to look through them and copy or re-use anything. This neural network layer class might contain some useful reference code.