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. :)


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


From: ironruby-core-boun...@rubyforge.org 
[mailto:ironruby-core-boun...@rubyforge.org] On Behalf Of Shay Friedman
Sent: Saturday, October 17, 2009 11:14 PM
To: ironruby-core@rubyforge.org
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 Friedman
Author of IronRuby Unleashed
http://www.IronShay.com
Follow me: http://twitter.com/ironshay
_______________________________________________
Ironruby-core mailing list
Ironruby-core@rubyforge.org
http://rubyforge.org/mailman/listinfo/ironruby-core

Reply via email to