Oracle Blog

Prashant Srinivasan's Weblog

Ruby, concurrency, and --enable-pthread

Ruby is a green threaded application. ie., irrespective of how many threads are running in the Ruby VM, there is only one operating system thread that the Ruby interpreter is scheduled on. Needless to say, this seriously limits the concurrency achievable with one Ruby VM - and quite naturally drives deployments to operate herds of web servers for their application, front ended by load balancers(like Pen, Pound etc.,) and deployed in an automated fashion(with the likes of Capistrano) - JRuby is way better at threading, though not as good as one would expect(see comments below for a clarification).

So why use --enable-pthread to configure/build Ruby?

Probably because building Ruby with support for a threaded extension(like Tcl/Tk on Solaris, which is compiled with support for pthreads) gives out a dire warning that compiling a threaded extension with a non-pthreaded Ruby will cause frequent crashes? Now that could potentially happen if the threaded extension changed the interpreters context and left it in an inconsistent state, but in practice that turns out not to be true(write a threaded extension and run it through a Ruby interpreter that is not pthread enabled to verify).

Using --enable-pthread has performance ramifications. In Ruby 1.8.6(which is still the sweet spot version in terms of security / stability) removing this option gives about a 2x boost in performance - this is due to the elimination of many getcontext and setcontext calls in the interpreter - these high overhead system calls are not used in the non-pthread enabled code, and avoiding pthreads does not impose a concurrency issue since Ruby is green threaded. further, using --enable-pthread increases the size of Ruby's stack to the point that the compiler has to use a more complicated algorithm(as opposed to simply decrementing the stack pointer) for alloca, further increasing the interpreters overhead.

How do you figure "JRuby is a little bit better at threading, though not much better than MRI"? First off, JRuby actually has real native threads, which Ruby 1.8 does not. And JRuby's threads actually run in parallel, which Ruby 1.9's do not. JRuby is a lot better at threading.

Not to be the contradictosaurus, but on my 2nd+ batch of httperfasauruses against the aforementioned slower interpretersaurus (with pthreadosaurus on and off), I show bench times being within around 5% of eachotherasauruses.

@Charles: Thanks for pointing that out, JRuby is definitely much better than MRI-1.8.6 at concurrency(I modified my original entry to make this clear) and most other tests.

It's easy to be better than MRI at threading. JRuby was just as bad prior to 1.1.1, and at 1.1.1, it began to scale(It had to do with local variables in the same class being addressed on the same cache line in jdk 6, so cache contention negated much of the scalability that using native threads brought in - and I believe you and Tom fixed this in a 1.1.1rc).

So, things became much better from that point, but the scalability erosion with the second thread was still about 50%, 80% with the third, and so on. Thats leaves some to be desired(compared to ideal scaling)

I haven't looked at how 1.1.4 behaves - let me fire those off and put out some data.

@alan - Interesting, I'm not sure what's happening on your setup. The get/setcontext stuff sits plumb in eval.c so not much chance of avoiding it with some workloads and getting it with others. I'd suggest profiling your code path with pthreads and without to verify what's happening in the interpreter.