Dipping a Toe in the C

7 February, 2007

Last night, I took an unexpected jacknife into the underground wellspring of C-code that burbles beneath the calm surface of Ruby. As part of some preliminary research for a super secret and incredibly exciting project I'm planning, I learned about two related libraries, Ruby2C and Ruby Inline, that dip into that pool to accomplish opposite, but complimentary, goals: translating Ruby into C for portability and flexibility, and writing custom Ruby methods in C in order to improve their performance.

Ruby was originally written in C and, for the most part, Ruby-related C coding is for the serious gray-bearded core language contributor. These two libraries give merely mortal programmers like me a chance to play with some of the power that comes from manipulating the language's internals. The downside of this dynamic is that I'm not smack dab in the center of the target audience for these projects the way I am with Rails and so I ran into a whole series of unaccustomed obstacles and inconveniences in the course of my dive including non-existent installation instructions, thin documentation, and incomplete, experimental code. While these may be familiar surroundings for the above-mentioned gray beards, they certainly aren't for me, and so I thought I'd take a moment to document what I learned about dealing with them on behalf of the next poor, desperately, Googling soul to follow in my footsteps.

Take Ruby2C, or, should I call it "ruby_to_ansi_c"? Part of the Metaruby project, which aims -- seemingly in a spirit of pure language geekery -- to rewrite Ruby's core classes in Ruby itself, this library provides machinery for translating ruby code into its C equivalent. Beyond whatever self-referentialist uses this might have, there's definitely a practical upside to the portability it provides. I don't want to tip my hand too thoroughly, but I can think of some neat places where I'd like to stick code that require C for entry.

'So far, this all sounds fine and dandy,' you say 'so what's the problem?' Well, my rhetorical question about the library's name hints at one facet of the unfriendliness involved. While the library usually calls itself Ruby2C in public, that is, in fact, almost never formally its name. Its rubygem goes by RubyToC and it includes two parallel libraries, one called "ruby_to_ansi_c" and one called "ruby_to_ruby_c". And, when it comes to code, names matter: installation, inclusion, and invocation are all impossible without getting them exactly right.

Another indicator of the size of the problem is the section of the README.txt that falls under the heading of Installation: "Um. Please don't install this crap yet..." So, I guess that leaves you with me. Anyway, without further ado, here's what I learned from installing Ruby2C and getting to hello world with it:

Install happens, like with pretty much any other gem (make sure you get the capitalization
), thusly:

$sudo gem install RubyToC

Now that we've got the library, let's write some code. We're going to write a class that, when translated into C will just print some text to the screen when compiled and run:

require 'ruby_to_ansi_c'
class MyTest
def say_hello
puts "hello"
end
def main
say_hello
return 0
end
end

Note the require line. The use of the "main" method is just a cute little hack from one of the RubyToC examples that makes the resulting program runnable. When translated, the new program will have a "main" function which is what gets called when you run a compiled C program from the command line.

Now, let's go ahead and do the translation:

result = RubyToAnsiC.translate_all_of MyTest
puts result

This produces the following C code:

long main();
void say_hello();
long
main() {
say_hello();
return 0;
}
void
say_hello() {
puts("hello");
}

That C source may look a little funny since the RubyToC generator isn't much for producing aesthetic whitespace, but it'll compile and run, which is what matters. To test it out, copy that snippet into my_c_test.c and fo the following:

$ gcc my_c_test.c -o my_c_test
$ ./my_c_test
hello

We could also just build the C for one individual method, like so:

RubyToAnsiC.translate(MyTest, "say_hello")

which would return just the second of the two functions in the above C source.

All of this is pretty simple and very powerful once you get it working. Of course, the code itself has a beautiful and clean user interface (in the form of these class 'translate' methods). It's just the websites and documentation that suck!

Now, the opposite of the ability to convert Ruby to C is the ability to write your own Ruby methods in C, just like the gray beards do. Unlike its converse, this process has obvious and wide-ranging benefits in the form of significant performance enhancements. Ruby is a high level language and not an especially zippy one. C, being closer to the machine, will almost always take care of an equivalent task in less time. The point of Ruby Inline is to let you rewrite the biggest performance choke points of your code in C to speed them along. Here's a basic usage example that adds an instance method called "say_hello" to our MyTest class that simply prints the text "hello" as many times as is asked:

require 'rubygems'
require 'inline'
class MyTest
inline do |builder|
builder.c <<-CODE
void say_hello(int i){
int n = 0;
while(n < i){
puts("hello");
n++;
}
}
CODE
end
end
MyTest.new.say_hello 10

While this is a trivial example, its form is a common one for optimization based on Ruby Inline: a central slow loop or algorithm that we plan on running many times.

A few things of note. Ruby Inline offers a number of options for including libraries, providing compilation instructions, and such. Take a good look at the documentation for details. Also, I've had some problems while playing with Ruby Inline in irb. They tend to take the form of "Errno::ENOENT: No such file or directory" errors. I don't think that this kind of code was really meant to run in a shell. It wants a source file to compile from and a static place to compile to. In addition, I think that wirble, a set of irb-enhancing tools I use, exacerbates things. In summary, run your Inline examples from files.

Tagged: , , , , ,