Pugilism in .NET ?

|

No, not that kind of boxing! At the end of a recent post I asked a question on Boxing, a term used in the .NET Framework to mean the process of converting a value type to a reference type, with un-boxing be the reverse process.

From a recent post:

...notice in the IL above [ED: below in *this* post] that the two Int32 values are boxed (note the box IL instruction) before they are used in the call to WriteLine(); how could the WriteLine() statement be re-written to avoid the boxing?

This was the IL in question:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] int32 hours,
        [1] int32 days)
    L_0000: nop
    L_0001: ldc.i4.s 0x18 // decimal value 24
    L_0003: stloc.0
    L_0004: ldsfld int32 Innovation.Program::MaxDaysOfTheWeek
    L_0009: stloc.1
    L_000a: ldstr "Hours: {0}, Days: {1}"
    L_000f: ldloc.0
    L_0010: box int32
    L_0015: ldloc.1
    L_0016: box int32
    L_001b: call void [mscorlib]System.Console::WriteLine(string, object, object)
    L_0020: nop
    L_0021: ret
}

The code that generated this IL is reproduced here to save you, dear reader, from having to flit back and forth between the two posts:

using System;
class Program
{
    public const int MaxHoursInADay = 24;
    public static readonly int MaxDaysOfTheWeek = 7;
    static void Main(string[] args)
    {
        int hours = Program.MaxHoursInADay;
        int days = Program.MaxDaysOfTheWeek;

        Console.WriteLine
            ("Hours: {0}, Days: {1}", hours, days);
    }
}

So first of all, you're probably asking:

What's the big deal, why do I want to remove the boxing operations?

Boxing can have negative performance implications for your code. However, with the introduction of Generics in .NET 2.0 a fair number of use cases that would cause boxing have gone the way of the Dodo and the ArrayList. But, when every cycle counts removing that additional overhead could help your application.

The solution is simple, but maybe a little surprising:

Console.WriteLine
    ("Hours: {0}, Days: {1}",
        hours.ToString(), days.ToString());

Eh? How does that work? Surely, to call ToString(), a method on the Object class i.e. a reference type, prevent boxing? Surely the runtime has to box the Int32 to make the call?!?

Well, no. In that sense it defies common sense. The reason is that Microsoft have provided an implementation of the ToString() method for Int32 via an override on the ToString() method. The C# compiler picks this up and because the compiler knows polymorphism cannot come into play, emits the code that calls ToString() directly; the upshot is that the value type does not have to be boxed (nice!).

Just to prove it, here's the IL output for the exact same program shown above, but this time with additional calls to the ToString() method emitted by the compiler, replacing the box instructions:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 3
.locals init (
[0]
int32 num,
[1]
int32 num2)
L_0000:
nop
L_0001:
ldc.i4.s 0x18
L_0003:
stloc.0
L_0004:
ldsfld int32 Program::MaxDaysOfTheWeek
L_0009: stloc.1
L_000a:
ldstr "Hours: {0}, Days: {1}"
L_000f:
ldloca.s num
L_0011: call instance string [mscorlib]System.Int32::ToString()
L_0016:
ldloca.s num2
L_0018: call instance string [mscorlib]System.Int32::ToString()
L_001d:
call void [mscorlib]System.Console::WriteLine(string, object, object)
L_0022:
nop
L_0023:
ret
}


And that is the simple, albeit surprising answer. If you have any other surprising little tips like this I would love to hear from you.

1 comment:

Anonymous said...

Thanks for the info