Friday, May 14, 2010

IL Tweaks

IL Tweaks

Normally NET developers don't have to know about IL and some of I know don't even want to, as this is a stack language that's close to asm, except that the register logic is hidden away and the constructs are simpler. But sometimes it's good to know what the compiler generates because numerous times out of backwards capability the generated code is not the fastest it can be, and it's good to know what to avoid.

There are numerous tweaks that can be made, like the new() constraint in generics, but one thing I would like to focus on is code branching and local copying of stack variables, consider the flowing code:

public int Method()
    int i = 0;
    return i;

Show Me The Hardcore:

Then let's use IL dasm to get out the opcodes:

ildasm Test.exe /

Our IL code looks like this:

.method public hidebysig instance int32
          Method() cil managed
    // Code size       13 (0xd)
    .maxstack  2
    .locals init (int32 V_0,
             int32 V_1)
    IL_0000:  nop
    IL_0001:  ldc.i4.0
    IL_0002:  stloc.0
    IL_0003:  ldloc.0
    IL_0004:  ldc.i4.1
    IL_0005:  add
    IL_0006:  stloc.0
    IL_0007:  ldloc.0
    IL_0008:  stloc.1
    IL_0009:  br.s       IL_000b

    IL_000b:  ldloc.1
    IL_000c:  ret
  } // end of method Program::Method

At this point there are two that are interesting, the first one is the br.s this opcode states that a branch is always true and it jumps to a given label and in this case this is simply the next instruction. Ans as you may know branching code is expensive. The next thing that you see is that there are two locals declared, although we have only one local in our C# code.

The code part:

IL_0008:  stloc.1
IL_0009:  br.s       IL_000b
IL_000b:  ldloc.1

in the context of your method can be skipped, as we only do a local copy of variable we incremented, why make a copy and a branch that always points to the next instruction? Now I cannot guarantee that this will work in all situations (removing the local copy), but i'm certain that deleting br.s will work every time.

So if we delete the local declaration along with the opcodes that we pointed out, we will speed up the method execution time, to compile our new IL we use the following:

ilasm /out:test.exe


This is not going to be a big performance improvement over this sample method but if you take into consideration a more complex method and identify the places where it could be optimized via IL then there should be a noticeable effect.

No comments: