ConvertTo vs Cast

Consider the following Ruby code:

i = 0
while (i < 1000)
i = i + 1
end

When run using IronRuby, the (i < 1000) comparison results in method
ConvertToInt32 being invoked to convert local i into an integer,
however, the i +1 operation simply casts i to be an integer.

(IronPython performs a cast in both cases to avoid the method call to
ConvertToInt32)

It appears that the difference in behaviour of these two method calls in
IronRuby is due to the fact that the first argument for the LessThan
method is not overloaded:

public static bool LessThan(int self, int other)

public static bool LessThan(CodeContext/!/ context, int self, object
other)

but the first argument for the Add method is overloaded:

public static object Add(int self, int other)

public static double Add(int self, double other)

public static object Add(CodeContext/!/ context, object self, object
other)

Note, this only affects performance, not correctness. Eliminating the
call to ConvertToInt32 makes the program execute about 10% faster.

As an aside, is there really a need for an overloaded Add method with a
self of type object rather than int?

Ironically, making the types stronger in this way would make the
performance worse with the current implementation.

Or am I completely mistaken?

Cheers, Wayne.

Regarding the aside, you can pass Float, Bignum, Complex or any other
class
you choose to define to the Fixnum#+ operator so you do need an overload
with object. I seem to remember it then coerces the fixnum to whatever
the
other type is and then calls + on the coerced object.

Without looking at the code I can’t comment on the main thrust of the
mail,
but shouldn’t the local variable i be a Fixnum from the word go and
therefore call the LessThan(int, int) method anyway?

Pete

From: [email protected]
[mailto:[email protected]] On Behalf Of Wayne K.
Sent: Tuesday,05 February 05, 2008 03:36
To: [email protected]
Subject: [Ironruby-core] ConvertTo vs Cast

Consider the following Ruby code:

i = 0

while (i < 1000)

i = i + 1

end

When run using IronRuby, the (i < 1000) comparison results in method
ConvertToInt32 being invoked to convert local i into an integer,
however,
the i +1 operation simply casts i to be an integer.

(IronPython performs a cast in both cases to avoid the method call to
ConvertToInt32)

It appears that the difference in behaviour of these two method calls in
IronRuby is due to the fact that the first argument for the LessThan
method
is not overloaded:

public static bool LessThan(int self, int other)

public static bool LessThan(CodeContext/!/ context, int self, object
other)

but the first argument for the Add method is overloaded:

public static object Add(int self, int other)

public static double Add(int self, double other)

public static object Add(CodeContext/!/ context, object self, object
other)

Note, this only affects performance, not correctness. Eliminating the
call
to ConvertToInt32 makes the program execute about 10% faster.

As an aside, is there really a need for an overloaded Add method with a
self
of type object rather than int?

Ironically, making the types stronger in this way would make the
performance
worse with the current implementation.

Or am I completely mistaken?

Cheers, Wayne.

From: [email protected]
[[email protected]] On Behalf Of Peter Bacon D.
[[email protected]]
Sent: Tuesday, 5 February 2008 6:37 PM
To: [email protected]
Subject: Re: [Ironruby-core] ConvertTo vs Cast
Regarding the aside, you can pass Float, Bignum, Complex or any other
class you choose to define to the Fixnum#+ operator so you do need an
overload with object. I seem to remember it then coerces the fixnum to
whatever the other type is and then calls + on the coerced object.

In Ruby 1.8.6:

Fixnum.instance_method("+’).bind(3.0)

produces:

‘bind’: bind argument must be an instance of Fixnum (TypeError)

Without looking at the code I can’t comment on the main thrust of the
mail, but shouldn’t the local variable i be a Fixnum from the word go
and therefore call the LessThan(int, int) method anyway?

Yes, LessThan(int, int) is called each time, but an unnecessary call is
also made to method ConvertToInt32 every time around the loop to convert
what is already an integer into an integer.

Cheers, Wayne.

What about this though?

class D

def +(other)

45

end

def coerce(other)

[self,self]

end

end

3 + D.new

=> 45

In Ruby 1.8.6:

Fixnum.instance_method("+’).bind(3.0)

produces:

‘bind’: bind argument must be an instance of Fixnum (TypeError)

Peter Bacon D.:

OK, so I think that this is a problem with the AST to code generation,
though I really don’t understand it deeply enough to say exactly where.

If you dump the ASTs from the ruby code the body of the loop looks
like
this:
(scary Martin AST dump elided)

Yep. Looks like a bug. Can you open one on this for us?

Thanks,
-John

OK, so I think that this is a problem with the AST to code generation,
though I really don’t understand it deeply enough to say exactly where.

If you dump the ASTs from the ruby code the body of the loop looks like
this:

{

(.bound i) = (Object)0;

.return .comma {

    .try {

        {

            (.bound #rfc).InLoop = (Boolean)True;

            {

                (.bound #skip-condition) = (Boolean)False;

                .for (; ; ) {

                    .try {

                        {

                            .if ((.bound #skip-condition) )

{(Void)(.bound #skip-condition) = (Boolean)False;

                            } .else {.if (.action (Boolean) 

ConvertTo
System.Boolean( // ConvertTo to System.Boolean

                                .action (Object) InvokeMember <( //

InvokeMember <(Instance, Simple) ReturnNonCallable, IsCallWithThis

                                    (.bound i)

                                    1000

                                )

                            ) ) {/*empty*/;

                            } .else {.break;

                            }}

                            {

                                (.bound i) = (Object).action 

(Object)
InvokeMember +( // InvokeMember +(Instance, Simple) ReturnNonCallable,
IsCallWithThis

                                    (.bound i)

                                    1

                                );

                            }

                        }

                    } .catch ( BlockUnwinder #unwinder) {

                        (.bound #skip-condition) = (.bound

#unwinder).IsRedo;

                    } .catch ( EvalUnwinder #unwinder) {

                        .if (((.bound #unwinder).Reason ==

(BlockReturnReason)Break) ) {{

                            (.bound #loop-result) = (.bound

#unwinder).ReturnValue;

                            .break;

                        }

                        } .else {

                        .throw ()

                        }}

                }

            }

        }

    } .finally {

        (.bound #rfc).InLoop = (Boolean)False;

    }

    (.bound #loop-result)

}

;

}

No mention of ConvertToInt32 there although it is noticeable that the
(.bound i) value is an object and not a Fixnum.

If you dump the generated code you get the following two methods for
invoking the Fixnum methods:

public static object $Ruby.Builtins.FixnumOps.LessThan(object[]
objArray1,
DynamicSite<object, int, object> site1, CodeContext context1, object
obj1,
int num1)

{

if (((obj1 != null) && (obj1.GetType() == typeof(int))) && 

(((RubyClass)
objArray1[0]).get_Version() == 0x3b3e))

{

    bool flag;

    bool flag1 = flag =

FixnumOps.LessThan(Converter.ConvertToInt32(obj1), num1);

    return RuntimeHelpers.BooleanToObject(flag);

}

return site1.UpdateBindingAndInvoke(context1, obj1, num1);

}

public static object $Ruby.Builtins.FixnumOps.Add(object[] objArray1,
DynamicSite<object, int, object> site1, CodeContext context1, object
obj1,
int num1)

{

if (((obj1 != null) && (obj1.GetType() == typeof(int))) && 

(((RubyClass)
objArray1[0]).get_Version() == 0x3b3e))

{

    return FixnumOps.Add((int) obj1, num1);

}

return site1.UpdateBindingAndInvoke(context1, obj1, num1);

}

Here as Wayne points out the code generator has added in the
ConvertToInt32
in the LessThan invocation, whereas in the Add invocation it has used a
direct cast. Somewhere in the innards of Ruby.dll this decision is
being
made and I haven’t tracked down where.

Clearly it is not necessary to use the conversion method: for a start
there
is a clause around the call to the method that tests whether obj1 (self)
is
an int.

As it happens I am generally doubtful about the inclusion of automated
Conversion in IronRuby at all, apart from perhaps for .NET classes that
don’t map directly to Ruby ones, such as byte and float. The design of
the
Ruby builtin classes manages the conversions directly, using such things
as
to_s, to_str, to_f, to_int and to_i. Getting the DLR to do hidden
conversions seems to be overkill and can upset the semantics of the
library
classes.

Pete

Without looking at the code I can’t comment on the main thrust of the
mail,
but shouldn’t the local variable i be a Fixnum from the word go and
therefore call the LessThan(int, int) method anyway?

Yes, LessThan(int, int) is called each time, but an unnecessary call is
also
made to method ConvertToInt32 every time around the loop to convert what
is
already an integer into an integer.

Cheers, Wayne.

Stupid question, but how do you dump the AST? I’ve wanted to do that a
couple times (most recently while hacking my own temporary “eval”).

On Feb 5, 2008 1:42 PM, Peter Bacon D. [email protected]

rbx /? is your friend :slight_smile:

the quick answer is rbx -X:ShowASTs

For reference:

IronRuby command line:

Usage: [options] [file|- [arguments]]

Options:
-opt dummy
-c cmd Program passed in as string (terminates
option list)
-h Display usage
-i Inspect interactively after running script
-V Print the version number and exit
-O Enable optimizations
-D EngineDebug mode
-OO Remove doc-strings in addition to the -O
optimizations
-X:AutoIndent
-X:AssembliesDir Set the directory for saving generated
assemblies
-X:ColorfulConsole Enable ColorfulConsole
-X:DumpASTs Dump all ASTs generated to a file
-X:ExceptionDetail Enable ExceptionDetail mode
-X:Interpret Enable interpreted mode
-X:Frames Generate custom frames
-X:GenerateAsSnippets Generate code to run in snippet mode
-X:ILDebug Output generated IL code to a text file
for debugging
-X:MaxRecursion Set the maximum recursion level
-X:NoOptimize Disable JIT optimization in generated code
-X:NoTraceback Do not emit traceback code
-X:PassExceptions Do not catch exceptions that are
unhandled by script code
-X:PrivateBinding Enable binding to private members
-X:SaveAssemblies Save generated assemblies
-X:ShowASTs Print all ASTs to the console
-X:ShowClrExceptions Display CLS Exception information
-X:ShowRules Show the AST for rules generated
-X:SlowOps Enable fast ops
-X:StaticMethods Generate static methods only
-X:TabCompletion Enable TabCompletion mode
-X:TrackPerformance Track performance sensitive areas
-X:CachePointersInApartment Cache COM pointers per apartment
(prototype)

On Feb 5, 2008 3:59 PM, Eric N. [email protected] wrote:


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


Michael L.
[Polymath Programmer]
http://michaeldotnet.blogspot.com

Hi Peter,

Yes, you are right, sorry - somehow I missed reading the very last case
in our fix_plus implementation :frowning:


From: [email protected]
[mailto:[email protected]] On Behalf Of Peter Bacon
Darwin
Sent: Wednesday, 6 February 2008 12:16 AM
To: [email protected]
Subject: Re: [Ironruby-core] ConvertTo vs Cast

What about this though?

class D
def +(other)
45
end
def coerce(other)
[self,self]
end
end

3 + D.new
=> 45

In Ruby 1.8.6:

Fixnum.instance_method("+').bind(3.0)

produces:

‘bind’: bind argument must be an instance of Fixnum (TypeError)