Ruby to C, a couple snippets

As you might already know, the Ruby interpreter you’re (probably) using (the one at ruby-lang.org) know as MRI, for Matz Ruby Interpreter, is written in C. The VM is entirely written in C and most of the standard libraries too. So it seems that the way to make code run faster is really to dive into C.

Now I hear you yell with your ears all red, eyes full of blood and a big vein pumping on your forehead: But it will be unmaintainable!

If everything seems under control, you’re not going fast enough

– Mario Andretti

The hard part is that the C API of MRI lacks documentation. You either have to search through the mailinglist, look at other people’s code or try to guess from Ruby’s code. I recently ported a bit of code from Ruby to C and here are a couple of Ruby to C mapping that might help you.

If you’ve never written or looked at a Ruby extension before, I suggestion reading Peter Cooper excellent and very simple tutorial on how to set things up.

Objects are VALUEs

my_var = nil
this_is_true = true
some_fixnum = 1

VALUE my_var = Qnil;
VALUE this_is_true = Qtrue;
VALUE some_fixnum = INT2FIX(1);

Strings

string = "hi"

VALUE string = rb_str_new2("hi");

/* Ruby string to C string */
char *s = RSTRING_PTR(str);
int len = RSTRING_LEN(str);

Object to C struct

In C you’ll probably want to store your data in a struct. Ruby provide some things to wrap a struct inside a Ruby object. Also, since Ruby is not aware of the stuff created in the C world we have to be pretty explicit about everything.

module MyModule
  class MyClass
    def close
      @closed = true
    end
  end
end

In C

/* This is called by Ruby GC when the object is freed
 * so free all resources used here. We hook it up
 * in the Data_Wrap_Struct call down there.  */
void my_obj_free(my_struct_t *s)
{
  free(s);
}

/* Called by Ruby then and instance of your class is created
 * hooked by rb_define_alloc_func. */
VALUE my_obj_alloc(VALUE klass)
{
  my_struct_t *s = ALLOC_N(my_struct_t, 1);
  /* This stores the struct inside the Ruby object, so you
   * can get the struct back on each method call. */
  return Data_Wrap_Struct(klass, NULL, my_obj_free, s);
}

/* The actual MyClass#close method, first argument is the
 * instance object. It's hook into our class by the rb_define_method
 * call in Init_ */
VALUE my_obj_close(VALUE self)
{
  my_struct_t *s;
  /* This is where we get the struct back from the Ruby object */
  Data_Get_Struct(self, my_struct_t, s);
  
  s->closed = 1;
}

/* Init_* is magically called by Ruby to bootstrap your extension,
 * it's like a main function if you will. */
void Init_name_of_your_extension()
{
  VALUE mMyModule = rb_define_module("MyModule");
  VALUE cMyClass = rb_define_class_under(mMyModule, "MyClass", rb_cObject);  

  rb_define_alloc_func(cMyClass, my_obj_alloc);
  rb_define_method(cMyClass, "close", my_obj_close, 0);
}

Calling methods

obj.method_name

result = begin
  obj.method_name(arg)
rescue
  #
end

rb_funcall(obj, rb_intern("method_name"), 0);
/* if an error is raised, result will be set to Qundef */
VALUE result = rb_funcall_rescue(obj, rb_intern("method_name"), 1, arg);

Blocks

my_ary.each do |i|
  puts i
end

VALUE i_each(VALUE elem, VALUE *obj)
{
  /* elem is the yielded object and obj is a pointer to obj down there v */
  printf("%d\n", FIX2INT(elem));
  return Qnil;
}
rb_iterate(rb_each, my_ary, i_each, (VALUE) obj);

Error handling

raise ArgumentError, "..."

rb_raise(rb_eArgError, "...");
/* To raise an error corresponding to the one in errno */
rb_sys_fail("oops");

Garbage Collection

When creating Ruby objects in the C world, Ruby is not aware of the scope of each, it’s impossible to track when a variable is used and when it’s not, so it’s impossible for the garbage collector to know when do leave or sweep objects. If you create a long live object, that will persist for several method calls you have to tell Ruby’s garbage collector when it’s in use and when you’re finished with it. If you don’t, Ruby GC will free your variable in the worst possible time, ending up with unthinkable tragedies around the world, leaving you homeless and poor for all eternity.

VALUE var = rb_str_new2("prout");

/* Tell Ruby GC this object is in use */
rb_gc_register_address(&var);
/* when you're finished you can unregister it and let the GC free it */
rb_gc_unregister_address(&var);

Note that you don’t need to do this if you only use the variable in one method since the control is not given back to Ruby during the method call. Also if the variable is still referenced in the Ruby world it’s already registered.

More, more, more!

This is far from a complete guide to MRI, you’ll need to dig the Ruby doxygen doc if you need more info.

Now lets port Rails to C!

20 Comments

Filed under ruby, tips, tutorial

20 responses to “Ruby to C, a couple snippets

  1. If you want to bundle all this in a gem, the newgem generator includes an ext_conf generator (script/generate ext_conf libname) to create an ext/libname/… + test/test_libname_extn.rb file so you can TDD your C.

  2. @Nate yeah I saw rocaml, sadly I don’t know OCaml yet, but the exceptions interception and type conversion does look very nice.

    @drnic very cool! I guess we really are ready to Rails to C then :/

  3. Hi Marc,

    I think you can add the following link: http://whytheluckystiff.net/ruby/pickaxe/html/ext_ruby.html, This is the chapter ‘Extending Ruby’ from PickAxe.

  4. Also I think you don’t need to worry about explicit garbage marking if your object is passed back to some ruby object, or contained within another object that is referenced, where the ‘larger, containing object’ has the gc_something[?] function which tells the GC ‘these are my children, don’t collect them!’ during a sweep. Something like that 🙂

  5. Andre

    Could you clarify a bit on the GC? What do you mean with “long lived objects”? Do I need to do something explicitely in code like

    VALUE
    foo(void)
    {
    VALUE blah = rb_ary_new();
    append_stuff_to(blah);
    return blah;
    }

    ?

    Thanks!

  6. @Andre: If the object is passed back to Ruby you don’t need to do anything. Like in your example, you don’t need to register the address, since you return blah to Ruby.
    Unless you write large part of your application in C you won’t need to bother w/ this.

  7. Andre

    Well, I actually am writing a large part of the application in C with the ruby API, because performance is really important. It’s an application that simulates message passing in a graph with thousands of nodes, and each message is a ruby array.

    I’m not doing anything explicit to work with the garbage collector, because I thought that it was only needed for global objects and C struct fields that are VALUEs. However, memory usage is increasing a lot when the program runs for a long time (it runs for a few days).

    I’m not sure if this is just ruby’s conservative GC that isn’t freeing my objects, or if there’s something I should be doing to let it do its job…

  8. If you needed to play w/ rb_register_address you’d have segfaults while accessing memory that has been freed by the GC.

    You might have a memory leak in your C code if it’s increasing rapidly. I use Valgrind to hunt mem leaks: http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/

  9. Andre

    ==19019== LEAK SUMMARY:
    ==19019== definitely lost: 0 bytes in 0 blocks.
    ==19019== possibly lost: 16 bytes in 1 blocks.
    ==19019== still reachable: 17,390,960 bytes in 377,007 blocks.

    That was for a single run… for two runs, “still reachable” grows:

    ==19126== LEAK SUMMARY:
    ==19126== definitely lost: 0 bytes in 0 blocks.
    ==19126== possibly lost: 16 bytes in 1 blocks.
    ==19126== still reachable: 19,404,620 bytes in 356,601 blocks.
    ==19126== suppressed: 0 bytes in 0 blocks.

    I guess this means there’s stuff being kept around that’s not being dereferenced, and the GC can’t do anything about it 😦

  10. Pingback: online casino gambling site

  11. Pingback: boost free ringtones

  12. thanks for this usefull information.

  13. Idon’t know why but my browser doesn’t show this page correctly…However,

  14. Very good page. I have added it to my RSS feed.

  15. your blog is very good,
    I like your blog.
    I will bookmark your blog updates to follow

  16. ther are lots of free ringtone sites on the internet if you just keep on searching on google“;

  17. Hey, thanks for that superb posting. Honestly, about 3 months in the past I started taking reading blogs and there may be just so much junk to choose from. I enjoy that you just put remarkable subject material out that’s clear and well-written. Decent luck and thanks for the wonderful page!

  18. Sorry for raising such a old topic but it seems like rb_funcall_rescue inaccessible from extensions. Is there something similar?

  19. Having read this I thought it was really informative. I appreciate you finding the time and energy to put this short article together. I once again find myself spending way too much time both reading and posting comments. But so what, it was still worthwhile!

Leave a comment