Introduction

You might be thinking that Ruby and Fortran is an odd mixture... and you're certainly correct. But in addition to being an exercise in hooking things together, I feel that the languages complement each other:

Ruby

Fortran

Elegant, expressive, & maleable... at the expense of speed and native support for matrix operations.

Awesome for numerical computing... at the expense of being rigid, unelegant and antiquated.

Has an extensive and vibrant community. (Modern codebase)

Historically established (Some things are only in Fortran) and rich (There are tons of Fortran programs sitting around on the web from years past.) (Large historical codebase)

Some features of our build script:

We set up three commands: 'rake test1', 'rake test2' and 'rake test3' for compilation (and in the third case, linking together) and 'rake clobber' to remove our generated files.

RbConfig provides the Ruby header locations and C compilation flags.

Both the Fortran and C files are compiled with fPIC. (It's included in CFLAGS.)

In the third case, the C and Fortran object code is linked into our sievemodule.so, linked against the GFortran, math and Ruby libraries.

With this in hand, we can proceed into the realm of C/Ruby integration...

Section 3: Calling C from Ruby

Calling C from Ruby is more complicated... Not only do we have to write our functional C code, but also have to create a ruby module & method using the tools provided with the Ruby header, and handle data types between the two languages. I strongly suggest looking over the Ruby/C Interop references to understand what's going on. This time, lets just print out a string.

Notes:

We do a forward-declaration for the sieve subroutine and initialize our module variable to Ruby-nil.

We build a ruby method which we package into a module and expose to Ruby below.

There's a little bit of data-type-dancing going on... we accept a 'number' iterations from Ruby which needs to be converted into a C-int. Then when packing our array to return to Ruby, we want to convert integers into fixed numbers.

Benchmarks and Conclusions

Methodology:

I tested three different implementations of the Sieve of Eratosthenes: My own Ruby/C/Fortran stack (shown above,) a pure Fortran implementation using the same core algorithm, and a pure Ruby implementation. (Codes for which have been lost but were completely basic implementations.)

Each program was tested against 10,000 iterations and 1,000,000 each. In addition, each run was repeated without I/O processing (printing of the prime numbers and determining which ones are prime.) For each circumstance, run time was measured with the Unix utility time 10 times and averaged (arithmetic mean.)

Implementation

Run time (10,000)

Run time (1,000,000)

Description

Pure Ruby

0.1566 (seconds)

1.7979 s

I/O included

Pure Ruby

0.0267 s

0.6064 s

Computation only

Pure Fortran

0.0138 s

2.0481 s

I/O included

Pure Fortran

0.003 s

0.0192 s

Computation only

Full stack

0.0393 s

1.404 s

I/O included

Full stack

0.0221 s

0.0592 s

Computation only

Conclusion:

Here we see that I/O is a significant factor, with Fortran actually coming in as the slowest when I/O is included. Naturally, it is lightning-fast again sans output. Note that this particular implementation of the Sieve requires the I/O routine to check which data are primes, increasing the load on that part of the full task while lessening the work done by the "computation only" circumstances.

Pure Ruby is on par with its C/Fortran extension given a small task with such an efficient algorithm. But after boosting the work up to 1 million integers, pure Ruby is left in the dust.

I conclude that a binary extension is definitely worth the time spent for Ruby scripts that need to do any significant calculation. We all already knew that, of course. I'm not sure that writing any new functions in Fortran is a better idea than writing them in C, because every new datatype is another torrent of complexity when you have to convert it back and forth.

Further investigation is needed to determine whether Fortran extensions would be even remotely robust. In addition, I intend to explore interfacing with existing legacy code, which is probably a much more viable use of Ruby/Fortran.

Thanks for reading! Hope you're inspired to play further with Fortran or Ruby.