CSC/ECE 517 Fall 2010/ch3 3i MM: Difference between revisions
Line 48: | Line 48: | ||
There is a really good page for SWIG examples using Ruby [http://www.goto.info.waseda.ac.jp/~fukusima/ruby/swig-examples/index.html here]. The first simple example is a good one though, and there a few things that could be added, which will be below. | There is a really good page for SWIG examples using Ruby [http://www.goto.info.waseda.ac.jp/~fukusima/ruby/swig-examples/index.html here]. The first simple example is a good one though, and there a few things that could be added, which will be below. | ||
==== | ==== Simple Example ==== | ||
This is a simple setup to create a "greatest common denominator" function in C, and use it in Ruby by way of a SWIG-generated wrapper <sup>[http://www.goto.info.waseda.ac.jp/~fukusima/ruby/swig-examples/simple/index.html]</sup>. This is example also adds a global variable, "Foo." | This is a simple setup to create a "greatest common denominator" function in C, and use it in Ruby by way of a SWIG-generated wrapper <sup>[http://www.goto.info.waseda.ac.jp/~fukusima/ruby/swig-examples/simple/index.html]</sup>. This is example also adds a global variable, "Foo." | ||
Revision as of 01:54, 8 October 2010
Introduction
Ruby is a high-level, dynamic programming language that is useful for everything from short scripting assignments, to entire web applications and desktop GUI applications. However, there are inherent downsides to using Ruby come because of its interpreted, dynamic nature. Using C code for some tasks can improve memory usage, and raw execution speed when compared to Ruby [1].
However, what if you could combine the good performance aspects of C, with with the dynamic elements of Ruby? It seems like it should be possible, since Ruby is actually written in C. Well, luckily the maintainers of Ruby have thought ahead, and have made that possible in several different ways. There ways to extend Ruby's capabilities with C code, and there ways to use Ruby functions within your own C application. This page will attempt to cover aspects of both of these mechanisms.
Using C from Ruby
Following are some overviews and examples of ways to create C or C++ extensions to Ruby.
Ruby C API
README.EXT
The README.EXT file contains the latest information and an overview of how to create Ruby extensions in C. It is an invaluable source of information, and is included in any source code distribution of Ruby. The link is to the latest HEAD version in Ruby's official subversion repository, but you may want to read the version that came with your installed version of Ruby.
In Unix-like distributions, for example, this file may be installed installed in '/usr/share/doc/ruby1.8-dev/README.EXT.gz'. To read, from a command prompt type:
> zless /usr/share/doc/ruby1.8-dev/README.EXT.gz
VALUE
In C, every variable has a type. In Ruby, everything is an object. To bridge the gap between C's static types and Ruby's dynamic objects, Ruby creators came up with the "VALUE" typedef in C. In the Ruby C API, when a variable is of type "VALUE, you know that it either comes from the Ruby side of the program, will be returned to Ruby, or will be used by the Ruby side in some form or fashion [2]. The typedef VALUE is defined in ruby.h, and is typically an unsigned long. The Ruby interpreter uses VALUE as either a pointer to a larger Ruby data type, or - in the case of more primitive types, such as Fixnums, booleans, and the NilClass - to actually the value itself.
Example
RubyInline
RubyInline is an availble gem that is designed to allow easy C extensions through C code embedded directly in Ruby as strings. These embedded C functions are compiled at runtime, and work just as fast as if they were standalone C extensions.
Installation
RubyInline is a separate gem, available through rubygems, and can be installed thusly:
> gem install RubyInline
Example
require 'rubygems' require 'inline' class Example inline(:C) do |builder| builder.c "int method_test1() { int x = 10; return x; }" end end puts Example.new.method_test1
SWIG
SWIG is a popular wrapper generator that can produce wrappers for C and C++ code in several high level programming languages, including Perl, Python, and Ruby.
Example
There is a really good page for SWIG examples using Ruby here. The first simple example is a good one though, and there a few things that could be added, which will be below.
Simple Example
This is a simple setup to create a "greatest common denominator" function in C, and use it in Ruby by way of a SWIG-generated wrapper [3]. This is example also adds a global variable, "Foo."
example.c
/* File : example.c */ /* A global variable */ double Foo = 3.0; /* Compute the greatest common divisor of positive integers */ int gcd(int x, int y) { int g; g = y; while (x > 0) { g = x; x = y % x; y = g; } return g; }
This snippet of C creates a function called "gcd," which takes two integers, and returns the greatest common denominator. It also declares a global variable, "Foo," which we would like to use from Ruby. Next, we must create a SWIG interface file so we can use it to generate the wrapper.
example.i
%module example extern int gcd(int x, int y); extern double Foo;
This is a very simply file, and it's all that we need to generate many different language wrappers for this C function. All we need to do is tell SWIG what the module name is, and declare what resemble forward values for the function, and the global value.
Generating example_wrap.c
Now we can generate the Ruby wrapper by running the following command:
swig -ruby example.i
This creates a very large C file called "example_wrap.c." It may be useful to look over this file, but there is a lot going on there. In particular, the "_wrap_gcd(int argc, VALUE *argv, VALUE self)" function that gets generated checks to makes sure it is called with the right number of values, and does argument type checking, which is very useful. SWIG also built setter and getter functions for the "Foo" variable, which is nice.
Building this example
Unfortunately there are no fancy make-generating scripts to use with SWIG. You generally have to create and maintain your own Makefile. Rather than a whole makefile, however, here is a simple compilation line to build this example:
gcc -shared -fPIC -L/usr/lib -lruby1.8 -I/usr/lib/ruby/1.8/x86_64-linux/ example_wrap.c example.c -o example.so
Error during compilation
Also unfortunately, when this wiki page author was trying out the code on his machine, there was compilation error related to the resolution of the "Foo" symbol in the "example_wrap.c" file generated by SWIG. This seems like an error in SWIG Version 1.3.40. The error was as follows:
example_wrap.c: In function ‘_wrap_Foo_get’: example_wrap.c:1953: error: ‘Foo’ undeclared (first use in this function) example_wrap.c:1953: error: (Each undeclared identifier is reported only once example_wrap.c:1953: error: for each function it appears in.) example_wrap.c: In function ‘_wrap_Foo_set’: example_wrap.c:1966: error: ‘Foo’ undeclared (first use in this function)
This was resolved by adding the following line somewhere before the first use of the Foo variable:
extern Foo;
If you receive the same error, try adding that line.
Performance Comparison
This is a short test to compare an iterative calculation of PI in both a C function, and Ruby. The test yielded quite surprising results. This is a relatively inefficient way to calculate PI, but it works, nonetheless.
C PI Calculation
#include "ruby.h" VALUE PiCalc_C = Qnil; static VALUE pi_calc_c(VALUE self) { int numPartitions = 12000; int circleCount = 0; double interval = 0, pi = 0; int i = 0, j = 0; double a, b; interval = 1.0/(double)numPartitions; for (i = 0; i < numPartitions; i++) { a = (i + .5)*interval; for (j = 0; j < numPartitions; j++) { b = (j + .5)*interval; if ((a*a + b*b) <= 1) circleCount++; } } pi = (double)(4*circleCount)/(numPartitions * numPartitions); return rb_float_new(pi); } // The initialization method for this module void Init_pi_calc_c() { PiCalc_C = rb_define_module("PiCalc_C"); rb_define_method(PiCalc_C, "pi_calc_c", pi_calc_c, 0); }
As you may have noticed, the Init_pi_calc_c() function simply creates a module called "PiCalc_C," instead of a class. It loads the "pi_calc_c" method into that module. Notice that the pi_calc_c(VALUE self) function still needs to take one argument no matter what.
RubyInline PI Calculation
require 'rubygems' require 'inline' module PiCalcRubyInline inline(:C) do |builder| builder.c " double pi_calc_rubyinline() { int numPartitions = 12000; int circleCount = 0; double interval = 0, pi = 0; int i = 0, j = 0; double a, b; interval = 1.0/(double)numPartitions; for (i = 0; i < numPartitions; i++) { a = (i + .5)*interval; for (j = 0; j < numPartitions; j++) { b = (j + .5)*interval; if ((a*a + b*b) <= 1) circleCount++; } } pi = (double)(4*circleCount)/(numPartitions * numPartitions); return pi; }" end end
Note that the RubyInline version is substantially similar to the C function, but it returns a double instead of a VALUE, and it does not need to specify an unused "self" parameter in its method signature.
Ruby PI Calculation
module PiCalcRuby def pi_calc_ruby numPartitions = 12000 circleCount = interval = pi = 0.0 interval = 1.0/numPartitions; for i in 0..numPartitions do a = (i + 0.5)*interval for j in 0..numPartitions do b = (j + 0.5)*interval if ((a*a + b*b) <= 1) then circleCount += 1 end end end pi = (4*circleCount)/(numPartitions * numPartitions) end end
As you can see, this is a relatively inefficient nested loop function that calculates PI. There is a fair amount of floating point arithmetic.
Results
Test Harness:
#!/usr/bin/env ruby require 'pi_calc_ruby' require 'pi_calc_c' require 'pi_calc_rubyinline' include PiCalcRuby include PiCalc_C include PiCalcRubyInline def c_test start = Time.now pi = pi_calc_c() stop = Time.now puts "C PI Result: #{pi}" puts "C Time: #{stop - start}" end def ruby_inline_test start = Time.now pi = pi_calc_rubyinline() stop = Time.now puts "RubyInline PI Result: #{pi}" puts "RubyInline Time: #{stop - start}" end def ruby_test start = Time.now pi = pi_calc_ruby() stop = Time.now puts "Ruby PI Result: #{pi}" puts "Ruby Time: #{stop - start}" end c_test ruby_inline_test ruby_test
Results for Ruby 1.8:
$ ruby1.8 perf_test.rb C Time: 1.193169 RubyInline Time: 1.192995 Ruby Time: 274.178188
Results for Ruby 1.9.1:
$ ruby1.9.1 -rubygems perf_test.rb C Time: 1.192857364 RubyInline Time: 1.192744329 Ruby Time: 175.468406517
As you can see from the test, Ruby-1.8 took 274 seconds to calculate PI, and Ruby-1.9.1 took 175.5 seconds, but what the C function and RubyInline did it in about 1.9 seconds. Note, all three PI calculation version produced the same PI value: 3.14159491666667. This algorithm was designed to calculate PI to 8 decimal points, so the rest is superfluous.
Using Ruby from C
README.EXT
Once again, this file, included in the Ruby source code, has a section on using Ruby features from C. Section 2.2 is a brief overview of some techniques for doing this.
Conclusion
Ruby is an excellent dynamic programming language that can tackle a wide range of problems, but it is always nice to be able to extend its functionality. When performance is a pretty big concern, the Ruby C API comes to the rescue. Ruby provides 'mkmf,' and a bevy of macros and functions that make it very easy to write C code that can be used from your Ruby application. RubyInline is a very convenient way to write a few functions in C as well. SWIG might be complicated, and might require some planning ahead in your C or C++ application to adapt it for use in a SWIG, but it does a good job of producing usable Ruby C API wrappers that be imported like any other of these methods.