CallSiteStorage for [RubyMethod]

Hi,

I’m working on a native extension by writing an IronRuby library in C#.
I
hit the wall with the CallSiteStorage parameters that can optionally
come as
the first 0 or more parameters for a ruby method.

What are they and what’s their use? I tried to understand that from
their
current uses accross the code by I couldn’t understand how IronRuby
knows
what to set there.

For example, this is from ArrayOps:
[RubyMethod(“sort”)]
public static object Sort(
BinaryOpStorage/!/ comparisonStorage,
BinaryOpStorage/!/ lessThanStorage,
BinaryOpStorage/!/ greaterThanStorage,
BlockParam block, RubyArray/!/ self) {


}

How does IronRuby knows that the first should be a comparison storage,
the
second a less-than storage and the third a greater-than storage? is it
because of the name of the parameter?

Thanks!
Shay.

A dynamic call site is an object that takes a certain number of
arguments and a kind of description of how to combine those arguments
into a result. The “description” (also known as the call site binder)
generates code that is stored in the call site and keyed on some
property of the arguments – usually the type. So a call site that’s
used to perform a “less than” operation must be attached to code that
knows how to look at two arguments and generate IL which will determine
whether the first arg is less than the second one (or, if the comparison
is not valid, IL which will throw an exception).

Now let’s say you have a bit of Ruby that says “c = a < b”. When the
IronRuby compiler generates code for this, it doesn’t know what the
types of “a” and “b” are, so it can’t emit the actual comparison.
Instead, it creates a call site that takes two arguments and returns a
result, and it attaches this call site to a binder that says “call the <
function that’s defined on the first object”. The first time the code is
executed, a = 1 and b = 3. The binder looks at the type of a and
determines that it should call Fixnum.<, so it creates code in the form
of a delegate which will do that (let’s name this “CALL1”), and will
store that delegate in the call site along with a test (“TEST1”) which
indicates when that delegate is valid.

The next time the code is executed, a = 3 and b = 7. The call site
executes TEST1, which returns a positive result so it uses CALL1 to
execute the comparison. The binder does not get involved.

The third time the code is executed, a = “hello” and b = “world”. TEST1
fails, so the call site asks the binder to analyze the new situation and
come up with a TEST2 and CALL2 that will call String.< for an argument
of type string.

All of this is basically DLR 101, so I’m sorry if it’s stuff you already
know :).

Now it should be pretty obvious that a call site’s performance degrades
as it sees a greater variety of types. The site has more and more TEST
methods to run before it gets a positive result or knows that it has to
fall back to the binder to generate new code.

Consider a method like Array.sort. This method needs to perform
comparisons on individual array elements of arbitrary type. That means
that a dynamic call site needs to be involved. In earlier versions of
IronRuby, we only had two choices – use a single call site that’s
shared between all calls to Array.sort, or create a new site each time
sort is called. The former approach suffers from the problem of “going
megamorphic”, that is, having too many tests to be called for each
comparison. But with the latter, we lose out on some efficiency because
we need to create a new call site and run the binder code at least once
for every call to sort.

The CallSiteStorage mechanism adds a third option. It allows the library
author to push the location of the call site cache up to the place where
Array.sort is actually called. This then creates a situation that is
much more like the one for “c = a < b”; there’s a one-for-one
relationship between the call site in user code and the call site cache.
This is a good level for caching, because any given array in user code
that calls “sort” is very likely to contain elements of only a single
type or small number of types.

So to answer your questions more specifically,

  1. The IronRuby compiler automatically creates three BinaryOpStorage
    objects for each call to Array.sort – one for each of the three
    parameters which have that type – and emits code to insert those
    parameter onto the call stack before calling ArrayOps.Sort.
  2. The BinaryOpStorage object is not tied to a specific operation at
    creation; it’s just a cache. It’s how the BinaryOpStorage is used that
    determines the kind of code stored in the cache.
  3. Specifically, if you trace the code path into Protocols.Compare and
    Protocols.ConvertCompareResult, you’ll see that these storages are
    initialized with calls to “<=>”, “<” and “>”, respectively.
  4. The names of the parameters are irrelevant. :slight_smile:

There’s a general lesson here – the DLR is like porridge; it’s best
when it’s neither too hot nor too cold.

From: [email protected]
[mailto:[email protected]] On Behalf Of Shay F.
Sent: Saturday, October 17, 2009 11:14 PM
To: [email protected]
Subject: [Ironruby-core] CallSiteStorage for [RubyMethod]

Hi,

I’m working on a native extension by writing an IronRuby library in C#.
I hit the wall with the CallSiteStorage parameters that can optionally
come as the first 0 or more parameters for a ruby method.

What are they and what’s their use? I tried to understand that from
their current uses accross the code by I couldn’t understand how
IronRuby knows what to set there.

For example, this is from ArrayOps:
[RubyMethod(“sort”)]
public static object Sort(
BinaryOpStorage/!/ comparisonStorage,
BinaryOpStorage/!/ lessThanStorage,
BinaryOpStorage/!/ greaterThanStorage,
BlockParam block, RubyArray/!/ self) {


}

How does IronRuby knows that the first should be a comparison storage,
the second a less-than storage and the third a greater-than storage? is
it because of the name of the parameter?

Thanks!
Shay.

Thanks so much Curt for the detailed reply!

Shay.

Glad you enjoyed it!

One small correction: ignore the specific usage of the word “delegate”
in the second paragraph. The gist is correct, but the technical details
are different. “Delegate” was left over from an earlier revision of the
text.

From: [email protected]
[mailto:[email protected]] On Behalf Of Shay F.
Sent: Monday, October 19, 2009 2:30 AM
To: [email protected]
Subject: Re: [Ironruby-core] CallSiteStorage for [RubyMethod]

Thanks so much Curt for the detailed reply!

Shay.
On Sun, Oct 18, 2009 at 3:52 PM, Curt H.
<[email protected]mailto:[email protected]> wrote:
A dynamic call site is an object that takes a certain number of
arguments and a kind of description of how to combine those arguments
into a result. The “description” (also known as the call site binder)
generates code that is stored in the call site and keyed on some
property of the arguments – usually the type. So a call site that’s
used to perform a “less than” operation must be attached to code that
knows how to look at two arguments and generate IL which will determine
whether the first arg is less than the second one (or, if the comparison
is not valid, IL which will throw an exception).

Now let’s say you have a bit of Ruby that says “c = a < b”. When the
IronRuby compiler generates code for this, it doesn’t know what the
types of “a” and “b” are, so it can’t emit the actual comparison.
Instead, it creates a call site that takes two arguments and returns a
result, and it attaches this call site to a binder that says “call the <
function that’s defined on the first object”. The first time the code is
executed, a = 1 and b = 3. The binder looks at the type of a and
determines that it should call Fixnum.<, so it creates code in the form
of a delegate which will do that (let’s name this “CALL1”), and will
store that delegate in the call site along with a test (“TEST1”) which
indicates when that delegate is valid.

The next time the code is executed, a = 3 and b = 7. The call site
executes TEST1, which returns a positive result so it uses CALL1 to
execute the comparison. The binder does not get involved.

The third time the code is executed, a = “hello” and b = “world”. TEST1
fails, so the call site asks the binder to analyze the new situation and
come up with a TEST2 and CALL2 that will call String.< for an argument
of type string.

All of this is basically DLR 101, so I’m sorry if it’s stuff you already
know :).

Now it should be pretty obvious that a call site’s performance degrades
as it sees a greater variety of types. The site has more and more TEST
methods to run before it gets a positive result or knows that it has to
fall back to the binder to generate new code.

Consider a method like Array.sort. This method needs to perform
comparisons on individual array elements of arbitrary type. That means
that a dynamic call site needs to be involved. In earlier versions of
IronRuby, we only had two choices – use a single call site that’s
shared between all calls to Array.sort, or create a new site each time
sort is called. The former approach suffers from the problem of “going
megamorphic”, that is, having too many tests to be called for each
comparison. But with the latter, we lose out on some efficiency because
we need to create a new call site and run the binder code at least once
for every call to sort.

The CallSiteStorage mechanism adds a third option. It allows the library
author to push the location of the call site cache up to the place where
Array.sort is actually called. This then creates a situation that is
much more like the one for “c = a < b”; there’s a one-for-one
relationship between the call site in user code and the call site cache.
This is a good level for caching, because any given array in user code
that calls “sort” is very likely to contain elements of only a single
type or small number of types.

So to answer your questions more specifically,

  1. The IronRuby compiler automatically creates three BinaryOpStorage
    objects for each call to Array.sort – one for each of the three
    parameters which have that type – and emits code to insert those
    parameter onto the call stack before calling ArrayOps.Sort.
  2. The BinaryOpStorage object is not tied to a specific operation at
    creation; it’s just a cache. It’s how the BinaryOpStorage is used that
    determines the kind of code stored in the cache.
  3. Specifically, if you trace the code path into Protocols.Compare and
    Protocols.ConvertCompareResult, you’ll see that these storages are
    initialized with calls to “<=>”, “<” and “>”, respectively.
  4. The names of the parameters are irrelevant. :slight_smile:

There’s a general lesson here – the DLR is like porridge; it’s best
when it’s neither too hot nor too cold.

From:
[email protected]mailto:[email protected]
[mailto:[email protected]mailto:[email protected]]
On Behalf Of Shay F.
Sent: Saturday, October 17, 2009 11:14 PM
To: [email protected]mailto:[email protected]
Subject: [Ironruby-core] CallSiteStorage for [RubyMethod]

Hi,

I’m working on a native extension by writing an IronRuby library in C#.
I hit the wall with the CallSiteStorage parameters that can optionally
come as the first 0 or more parameters for a ruby method.

What are they and what’s their use? I tried to understand that from
their current uses accross the code by I couldn’t understand how
IronRuby knows what to set there.

For example, this is from ArrayOps:
[RubyMethod(“sort”)]
public static object Sort(
BinaryOpStorage/!/ comparisonStorage,
BinaryOpStorage/!/ lessThanStorage,
BinaryOpStorage/!/ greaterThanStorage,
BlockParam block, RubyArray/!/ self) {


}

How does IronRuby knows that the first should be a comparison storage,
the second a less-than storage and the third a greater-than storage? is
it because of the name of the parameter?

Thanks!
Shay.

Shay F.
Author of IronRuby Unleashed
http://www.IronShay.com
Follow me: http://twitter.com/ironshay


Ironruby-core mailing list
[email protected]mailto:[email protected]
http://rubyforge.org/mailman/listinfo/ironruby-core