Saturday, March 08, 2008

RubyInline for JRuby? Easy!

With JRuby 1.1 approaching, performance looking great, and general Ruby compatibility better than its ever been, we've started to branch out into more libraries and applications. So a couple days ago, I thought I'd make good on a promise to myself and have a look at getting RubyInline working on JRuby.

RubyInline is a library by Ryan "zenspider" Davis which allows you to embed snippits of C code into your Ruby scripts. RubyInline does a minimal parse of that source, and based on the function signature you provide it wires it to the containing class as a Ruby method entry point and performs appropriate entry and exit type conversion to whatever C types you happen to use. It's particularly useful if you have a small algorithm you need to run fast, and you'd like to run it in a somewhat faster language by "throwing work over" to it.

Here's the example of RubyInline from Ryan's page:

class MyTest

def factorial(n)
f = 1
n.downto(2) { |x| f *= x }
f
end

inline do |builder|
builder.c "
long factorial_c(int max) {
int i=max, result=1;
while (i >= 2) { result *= i--; }
return result;
}"
end
end

The interesting bit is the builder.c call, one of several functions on the C builder. Others allow you to add arbitrary preamble code, imports, and "bare" methods (with no type conversion) among other things. And as you'd expect, performance is greatly improved by writing some algorithms in C instead of Ruby.

Naturally, we want to have the same thing work in Java, and ideally use the same API and the same RubyInline plumbing for the rest of it. So then, I present to you java_inline, a RubyInline builder for JRuby!

This represents about four hours of work, and although it doesn't yet have a complete complement of tests and could use some "robustification", it's already working in about 100 lines of code. It's made far easier in JRuby than in C Ruby because we already have a full-features Java integration layer to handle the argument mapping.

Here's a sample java_inline script to show what it looks like, similar to the fastmath.rb sample provided with RubyInline.

So how does it work? Well the Ruby side of things is largely the same as RubyInline's C builder...parse signature, compose the code together and compile it, and bind the method. It wires directly into the RubyInline pipeline, so all you need to do is install RubyInline, require the java_inline.rb script and you're all set. On the Java side, it's using the Java Compiler API, provided in Java 6 implementations. (OT: This has to be the worst-designed API I've ever seen. Go see for yourself. It's cruel and unusual. I won't dwell on it.) So yes, this will only work on Java 6. Deal with it...or submit a patch to get it working on Java 5 as well :)

It's not released in any official form yet, but I'll probably try to wire up a gem or something. I have to make sure I'm dotting my eyes and crossing my tees when I release stuff, even if it's only 100loc. But the repository is obviously public, so play with it, submit patches and improvements, and let me know if you'd like to use it or help work on it more. I'm also interest in suggestions for other libraries you'd like to see special JRuby support for, so pass that along too.

11 comments:

Anonymous said...

This is so useful, Charles, you should consider making it part of teh standard jruby distribution.

Anonymous said...

That's great news. Could you include a result from the benchmark in simple math file?

Offtopic: do you have a road map for post 1.1? The one on the wiki seems to be a bit stale now that 1.1 is around the corner.

Anonymous said...

C:\jruby\progs\inline>jruby fastmath.rb
gives following error:

c:\jruby\progs\inline\tmp is insecure (40777). It may not be group or world writ
able. Exiting.

That directory is INLINEDIR

However, restricting or expanding permissions for INLINEDIR seems to have no effect...

Anonymous said...

I have established that the problem lies with the RubyInline gem on which "java_inline.rb" depends. RubyInline gem does not work well with Windows.

However, if the user simply wants to try out "java_inline.rb" and if security is not an issue then the simplest solution is as follows.

Find the file "inline.rb" in the RubyInline gem, and
(i) delete entirely the method "self.rootdir"
(ii) replace the method "self.directory" as follows
def self.directory
@@directory = "c:/jruby/progs/inline/tmp"
@@directory
end

This work-around simply hard-codes the location of the RubyInline cache into the directory "c:/jruby/progs/inline/tmp".
There is no need to set any environment variables.

It is assumed that "java_inline.rb" and "fastmath.rb" live in c:\jruby\progs\inline.

Go to that directory, type "jruby fastmath.rb" and everything should work.

Charles Oliver Nutter said...

All anonymous this time....ok, I'm game.

@anonymous1: That's certainly an interesting idea. It probably won't be in 1.1, but we could consider it for follow-up releases. I'd like for more people to try it out and help it bake before including it in an official release.

@anonymous2: Yeah, the wiki is a pain to keep up to date, but it does need some edits. Feel free to make any changes you can, and we'll get a post 1.1 roadmap out soon.

@anonymous3/4: Thanks for tracking that down. Could you file a bug in JRuby's bug repository for this? I know that RubyInline "works" on Windows for some definition of "works", so this could very easily be a behavioral mismatch in JRuby. At any rate, I'd like to track it.

Juan said...

Hello Charles:
The C code will be debugged step to step, like the Jruby code?
Many thanks. :-)

Charles Oliver Nutter said...

@juan Yeah, I wish...I think we have a while to wait for all-in-one debugging through ruby and java in the same tool. But it will come.

James Moore said...

"I'm also interest in suggestions for other libraries you'd like to see special JRuby support for, so pass that along too."

Hadoop and Nutch are at the top of my list for the next couple weeks. Seems like there's some room for interesting JRuby support there. (Not sure what, since I'm still in the "frantic reading" stage of the project...)

Oleg Andreev said...

I tried this on Mac OS X 10.5.1 and got an error.

./vendor/java_inline.rb:19: cannot load Java class javax.tools.ToolProvider (NameError)

I'm just playing with optimizations framework inside StrokeDB, i'm not a Java guru at all. I have no idea what to do with this error.

Hope this helps:

$ java -showversion
java version "1.5.0_13"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05-237)
Java HotSpot(TM) Client VM (build 1.5.0_13-119, mixed mode, sharing)

$ uname -a
Darwin oleganza.local 9.1.0 Darwin Kernel Version 9.1.0: Wed Oct 31 17:46:22 PDT 2007; root:xnu-1228.0.2~1/RELEASE_I386 i386 i386 MacBook2,1 Darwin

Oleg Andreev said...

BTW. Maybe, my jruby version is a bit old.

$ jruby -v
ruby 1.8.6 (2008-01-07 rev 5512) [i386-jruby1.1RC1]

Charles Oliver Nutter said...

@oleg This requires a Java 6 implementation that has the Java Compiler API built in. It won't currently run on anything lower than Java 6.