Archive by Author

Native MRI Callback

A few weeks ago after a discussion with raggi (yet again) about callback implementations for the Ruby language, we kicked off an attempt at a minimal native object that’s very close in performance to method dispatch.

Here’s a representation of the pure ruby version :

The game plan

The following inefficiencies would have to be addressed :

  • Instance variables Instance variable lookups in general require some symbol table overhead.
  • Block procedure arguments Block proc arguments are up to 6x slower than passing explicit objects.
  • Method dispatch The splat operator and __send__, although efficient, does have some noticeable overheads.
  • Kernel convenience method
  • Additional method dispatch in the critical path through this global function.

Ivar lookup overhead is mostly negated for Ruby 1.9 as the RObject struct stores the first three ivars for each object.

Implementation

The callback structure

We introduce a new callback struct with 2 members :

  • object Ruby object to invoke the callback on
  • method Method to call.Defaults to #call if no method argument given

Wrapping the C struct as T_DATA has the least overheads in accessing either the object and the method when our callback fires.

GC integration’s a piece of cake as well.

Allocation

Wrapping C structures requires an allocator function (Callback.allocate) to initialize our callback struct and register our mark and free functions with the GC.This is usually invoked prior to #initialize and is transparent for our use cases.

Definition

Three definition (setup) styles supported :

  • Object and method arguments
  • Block argument
  • Farmed off an existing object

We handle each of the argument or block definition styles in an initialize method and handle edge cases such as not assigning a callback method that’s not defined for the object.It’s also important to ensure that our callback is frozen at this point ( I’m looking at you lifo, this one’s legit :-) ) for consistency.

Our global function, Callback(), which is defined much like the popular Array() and Float() coercion helpers, and the farm out API reuse the alloc and init covered above.

Results

An intuitive callback API that’s slower than method dispatch, but faster than lambda callbacks.

Have a look at the source or install the gem to play around.

sudo gem install methodmissing-callback

Tested on MRI versions > 1.8.6 and 1.9.2 – please have a look through the test suite for further examples.

Follow me @ github or twitter if you enjoyed this article.Thanks for reading and special thanks to Aman Gupta for review !

Ruby Memory Allocation

During a recent discussion at work, my ever so sharp coworker James Tucker noted the significant void between user code and low level optimizations.

Micro benchmarks and rubyspec compliance are frequently referenced to compare implementations and quite often cited in random rants as well.What happens at that intermediate, libc, level ?

In this post we’ll explore the ISO C memory allocation / deallocation functions as invoked by the following interpreters :

* ruby-1.8.7-p72 (./configure)
* ruby-1.9.1-p129 (./configure)
* jruby-1.3.1 (binary distribution)

Requirements

* A DTrace enabled OS (OS X Leopard, Solaris etc.)
* One or more interpreters installed
* A local checkout of extlib

I’ve opted to NOT test with the latest 1.9.2 preview – see why

The DTrace PID provider

A set of C level probes is spawned for the lifetime of a given command or existing process.When the process dies, the probe’s is unset.You don’t have to recompile or relink – it just works.See the Sun wiki for further information.

This is very different from static probes that ships with MySQL 6 or a previously released patched tree by Joyent.

Memory Allocation

Regardless of the underlying GC implementation, there’s three ISO C functions that handle memory allocation.

* malloc allocates X bytes of memory
* calloc allocates memory for X objects of Y size each
* realloc shrinks or expands the size of a previously allocated memory region

Each allocation function returns a pointer to the allocated (or reallocated) memory region which can be released with the free function call.

Of note is that most implementations never release memory from the heap back to the underlying OS – it’s assigned to a pool for reallocation to reduce system call (sbrk) overhead of the current process.

Toolbox

Ruby 1.8.7

I happen to use this as my stock 1.8 series interpreter – 1.8.6 should yield similar output.

Ruby 1.9.1

JRuby 1.3.1

Again, I’ve noticed a 1.4 version flying around, but happened to have 1.3.1 installed.

Passing Thoughts

Note the liberal memory allocation overheads on the 1.8 series, but an otherwise overall similar distribution pattern to 1.9.I can’t comment much on the JVM case due to limited Java experience.Anyone care to chime in ?

This is an exercise to test the waters for a potential audience for these experiments.I have a few contexts lined up, with a specific MRI focus, but can get into the bowels of most things.Suggestions and thoughts welcome.