RubyInline: September 2006 Archives

Recursive Functions in RubyInline

| | Comments (2)

harrisj filed a bug against inline today regarding recursive functions failing. The simplest example is fibonacci. The problem is simple, and its solution (workaround really) is simple too. Let's dig in, shall we?

Pure Ruby

class Fib
  def rubyfib(n)
    return 1 if n <= 1
    rubyfib(n-1) + nfib(n-2)
  end
end

Simple enough example, but because of its recursive nature it takes a long time to run (less to do with its recursiveness than the simple fact of the matter that it is doing a ton of method dispatches in general.

Inline C

So, let's say that we have this code coming up at the top of our profile and we want to speed it up. We grab our trusty inline and pop in the equivalent in C:

require 'inline'
class Fib
  inline do |builder|
    builder.c <<-END
      long badfib(long n) {
        if (n <= 1) {
          return 1;
        } else {
          return badfib(n-1) + badfib(n-2);
        }
      }
    END
  end
end

The Recursion Problem

The problem is that this doesn't compile properly. Why? It looks right. Indeed it does, but you need to dig deeper to understand what inline is doing for you to make this stuff look so easy. What it is really generating is this:

static VALUE badfib(VALUE self, VALUE _n) {
  long n = NUM2INT(_n);

  if (n == 0 || n == 1) {
    return INT2NUM(1);
  } else {
    return INT2NUM(badfib(n-1) + badfib(n-2));
  }
}

The problem is twofold. badfib has two arguments, not one, and both of them are VALUE, not long. The "simple" solution is to pass in self, but you still have to convert the second arg as well and the return value. The last line starts to look gross:

  return FIX2INT(badfib(self, INT2FIX(n-1))) + FIX2INT(badfib(self, INT2FIX(n-2)));

That is icky. It works, is damn fast compared to the pure ruby example (14.5s vs 0.3s in my benchmarks), but it is still icky. I'll even take a speed hit to reduce icky. That said, there is a much simpler solution.

Pure C Recursion, Ruby Wrapper

Instead of mixing the two languages at that level (and it really is an oil and water problem at this stage--maybe if we shake it enough we'll get a vinaigrette), strictly separate them:

builder.prefix <<-END
  long realfib(long n) {
    if (n <= 1) {
      return 1;
    } else {
      return realfib(n-1) + realfib(n-2);
    }
  }
END

builder.c <<-END
  long cfib(long n) {
    return realfib(n);
  }
END

Here we've popped the real work into builder.prefix instead of builder.c which just injects the code raw. As a result, we have one layer doing ruby-to-c-to-ruby conversions and another doing the work. It is even faster because you get rid of all those icky conversions at every level. Even the resulting code looks much better:

long realfib(long n) {
  if (n <= 1) {
    return 1;
  } else {
    return realfib(n-1) + realfib(n-2);
  }
}

static VALUE cfib(VALUE self, VALUE _n) {
  long n = NUM2INT(_n);

  return INT2NUM(realfib(n));
}

Just one problem

There is one problem I want to point out here. Granted, the problem itself is slightly contrived, but I see examples like this coming up over and over in IRC and the mailing list. The problem is this: people get so myopically focused on using C to make things faster that they don't bother looking at their algorithms or data-structures. It is sad. Ruby may be slow for method dispatch, but bad code can be slow in ANY language. Wanna see a simple and readable pure ruby solution to the above that kicks the crap out of my fastest C solution?

##
# Classic fibonacci is boring, cache the hell out of it.
@@fib = {}
def fasterfib(n)
  return 1 if n <= 1
  unless @@fib.has_key? n then
    @@fib[n] = fasterfib(n-1) + fasterfib(n-2)
  end
  @@fib[n]
end

C doesn't make ruby fast. Avoiding method dispatch makes ruby fast. You can do that using pure ruby quite a bit of the time by applying your noodle. Check out the times:

% ./fib.rb 10000 15
# of iterations = 10000
                          user     system      total        real
null_time             0.000000   0.000000   0.000000 (  0.001779)
fib-ruby             14.450000   0.060000  14.510000 ( 14.628854)
badfib                0.310000   0.000000   0.310000 (  0.318015)
cfib                  0.150000   0.000000   0.150000 (  0.147117)
fib-cached            0.010000   0.000000   0.010000 (  0.012523)

And Zed, before you get started, I picked 10k because that was all I had the patience to sit through. This isn't a scientific writeup. So nyah!

RubyInline version 3.6.0 has been released! Only 10 month late! (ugh I can be so spacy sometimes!)

http://www.zenspider.com/ZSS/Products/RubyInline/
http://rubyforge.org/projects/rubyinline/

Description:

Ruby Inline is an analog to Perl's Inline::C. Out of the box, it
allows you to embed C/++ external module code in your ruby script
directly. By writing simple builder classes, you can teach how to cope
with new languages (fortran, perl, whatever). The code is compiled and
run on the fly when needed.

Using the package_inline tool Inline now allows you to package up
your inlined object code for distribution to systems without a
compiler (read: windows)!

Features/Problems:

+ Quick and easy inlining of your C or C++ code embedded in your ruby script.
+ Extendable to work with other languages.
+ Automatic conversion between ruby and C basic types
+ char, unsigned, unsigned int, char *, int, long, unsigned long
+ inline_c_raw exists for when the automatic conversion isn't sufficient.
+ Only recompiles if the inlined code has changed.
+ Pretends to be secure.
+ Only requires standard ruby libraries, nothing extra to download.
+ Can generate a basic Rakefile and package up built extensions for distribution.

http://www.zenspider.com/ZSS/Products/RubyInline/
http://rubyforge.org/projects/rubyinline/

Changes:

+ 6 minor enhancements
+ C builder can now be used directly for other foreign language glue.
+ Pretty much all (c) functions are plain argument style, not argc/argv.
+ Added Nathaniel and Dan's patches for windows support.
+ Added VALUE as a default known type.
+ Improved testing under $DEBUG.
+ Deprecated $INLINE_FLAGS and $INLINE_LIBS are dead.
+ 3 bug fixes
+ Fixed a number of issues wrt testing.
+ Cleaned up and cached certain calculations.
+ Some windows compiler fixes went in, but MS compiler is a PITA still.

http://www.zenspider.com/ZSS/Products/RubyInline/
http://rubyforge.org/projects/rubyinline/

RubyInline Myths

| | Comments (4)

I just read the RubyInline section (22.4) of the Ruby Cookbook by Lucas Carlson and Leonard Richardson. I'm a bit ticked off so calibrate accordingly. In the discussion there are blatant errors. A lot of them... like, of the 8 paragraphs involved, 2 of them are basic high-level description, 1 of them is a warning against using C code to begin with (sound advice--although a bit off the mark since Inline does any language it is taught to do, not just C/++). Then there is the rest: 3 of the 8 paragraphs are flat out wrong and 2 more don't have the limitations indicated and contradict themselves.

First in says, RubyInline only understands a limited subset of C and C++. The functions you embed can only accept and return arguments of the types char, unsigned, unsigned int, char *, int, long, and unsigned long. If you need to use other types, RubyInline won't be able to automatically generate the wrapper functions. [...]. This is false. Completely and totally false. It isn't even the full list of registered automatically converted types, and since 3.0.0 (released 2003-12-23) there has been public API for registering conversions of your own. Before then it was possible but less easy. Example:

inline(:C) do |builder|
  builder.add_type_converter("VALUE", '', '') # register, but doesn't require conversion
  # ...
end

Second, it goes on to mention that another limitation is that you need to have a complier environment but then says that RubyInline provides inline_package to deal with that... well... is it a limitation or not? Seems they're saying not, except that it is in the list of limitations. Way to put on some negative spin and backpedal one paragraph later. *shrug*

To their credit, Neither Lucas nor Leonard wrote this section. Instead, Garrett Rooney has been given credit for this section, and all other sections for externalized code (except jruby). After doing some googling it looks like Garrett has a stake SWIG, so I have to assume that there is some bias involved.

None of the three sent me the section for review or notified me in any way that this was happening. I'm a bit disappointed, but I guess not that surprised given the publisher. I just wish they'd sent me a draft to review. It would have been so easy to deal with proactively.

About this Archive

This page is a archive of entries in the RubyInline category from September 2006.

RubyInline: August 2006 is the previous archive.

RubyInline: October 2006 is the next archive.

Find recent content on the main index or look in the archives to find all content.

Pages

Powered by Movable Type 4.32-en