Archives

Archives / 2009 / February
  • ILDasm Helpful Hack

    I do not want to learn "yet another programming language", especially a low-level one like Microsoft's Intermediate Language (IL). And yet, sometimes it's useful to look under the hood and see what in tarnation is going on. And that means knowing something about IL.

    I was answering a question on optimization (the root cause of a lot of unmaintainable code) and found it useful to generate the IL to show which of two algorithms was quicker. With modern operating systems having dozens of background threads running, simple timing can lead to misleading results. Is the algorithm slow, or was System Restore creating a restore point when we measured?

    In order to examine code in IL, it's helpful to delimit sections. But we can't use comments, they are stripped out. So, here is a cheesy way to delimit sections of code. Note the variables assigned to MyInt:

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {                      
                string MyString = string.Empty;
                int MyInt;
     
                MyInt = 0xAAAAAAA; // ---- Marker: algorithm A
     
                if (MyString == String.Empty)
                    MyInt = 1;
                else
                    MyInt = 2;
     
                MyInt = 0xBBBBBBB;  // ---- Marker: algorithm B
     
                MyInt = MyString == String.Empty ? 1 : 2;
     
                MyInt = 0xCCCCCCC;  // ---- Marker: end test.
            }
        }

    When you create the IL with ILDasm, you can easily see where the markers are. In this case I bolded and underlined them:

      .method private hidebysig static void  Main(string[] args) cil managed
      {
        .entrypoint
        // Code size       68 (0x44)
        .maxstack  2
        .locals init ([0] string MyString,
                 [1] int32 MyInt,
                 [2] bool CS$4$0000)
        IL_0000:  nop
        IL_0001:  ldsfld     string [mscorlib]System.String::Empty
        IL_0006:  stloc.0
        IL_0007:  ldc.i4     0xaaaaaaa
        IL_000c:  stloc.1
        IL_000d:  ldloc.0
        IL_000e:  ldsfld     string [mscorlib]System.String::Empty
        IL_0013:  call       bool [mscorlib]System.String::op_Equality(string,string)
        IL_0018:  ldc.i4.0
        IL_0019:  ceq
        IL_001b:  stloc.2
        IL_001c:  ldloc.2
        IL_001d:  brtrue.s   IL_0023
        IL_001f:  ldc.i4.1
        IL_0020:  stloc.1
        IL_0021:  br.s       IL_0025
        IL_0023:  ldc.i4.2
        IL_0024:  stloc.1
        IL_0025:  ldc.i4     0xbbbbbbb
        IL_002a:  stloc.1
        IL_002b:  ldloc.0
        IL_002c:  ldsfld     string [mscorlib]System.String::Empty
        IL_0031:  call       bool [mscorlib]System.String::op_Equality(string, string)
        IL_0036:  brtrue.s   IL_003b
        IL_0038:  ldc.i4.2
        IL_0039:  br.s       IL_003c
        IL_003b:  ldc.i4.1
        IL_003c:  stloc.1
        IL_003d:  ldc.i4     0xccccccc
        IL_0042:  stloc.1
        IL_0043:  ret
      } // end of method Program::Main
     

    A quick look at the IL code should tell you which algorithm is fastest…and you don't even have to be a rocket surgeon.

    But, of course, as any seasoned developer will tell you: "It's the maintenance stupid!"

    I hope you find this helpful.

    Steve Wellens

    [Update]

    My friend Svante pointed out to me that it is the JIT that does the final optimization and you can’t really go by IL to determine which algorithms are quicker.  He is correct.

    To look at the JIT output…

    Go to Tools->Options->Debugging->General and unclick "Suppress JIT optimization on module load."  You want the optimization.
    Add a breakpoint to the code. Run the program, when the breakpoint hits, right-click the source code and select "Go to Disassembly."   I removed the "markers" because they are no longer needed.

    JIT Output (edited):

                    if (MyString == String.Empty)
        00000050  mov         edx,dword ptr ds:[02CC102Ch]
        00000056  mov         ecx,dword ptr [ebp-40h]
        00000059  call        6A625FB8
        0000005e  mov         dword ptr [ebp-4Ch],eax
        00000061  cmp         dword ptr [ebp-4Ch],0
        00000065  sete        al  
        00000068  movzx       eax,al
        0000006b  mov         dword ptr [ebp-48h],eax
        0000006e  cmp         dword ptr [ebp-48h],0
        00000072  jne         0000007E
                        MyInt = 1;
        00000074  mov         dword ptr [ebp-44h],1
        0000007b  nop             
        0000007c  jmp         00000085
                    else
                        MyInt = 2;
        0000007e  mov         dword ptr [ebp-44h],2

     

                    MyInt = MyString == String.Empty ? 1 : 2;
        0000008c  mov         edx,dword ptr ds:[02CC102Ch]
        00000092  mov         ecx,dword ptr [ebp-40h]
        00000095  call        6A625FB8
        0000009a  mov         dword ptr [ebp-50h],eax
        0000009d  cmp         dword ptr [ebp-50h],0
        000000a1  jne         000000AD
        000000a3  nop             
        000000a4  mov         dword ptr [ebp-54h],2
        000000ab  jmp         000000B4
        000000ad  mov         dword ptr [ebp-54h],1
        000000b4  mov         eax,dword ptr [ebp-54h]
        000000b7  mov         dword ptr [ebp-44h],eax

    I hope this extra information is helpful to someone.

  • C# Nullable Types…Subtlety

    While moderating posts over on the Asp.Net forums, I ran into a thread containing questions about using Nullable types.

    Nullable types are a simple concept: Allow value types to have the value of null. Typically, an integer or float cannot be null: When in scope, they always exist and therefore must have a numeric value (zero is not null, it is a numeric value).

    Here is the subtlety (or in technical terms, a really ooky statement):

    Nullable types with the value of null are not really null.

    Look at this code:

        int    Test1 = 0;       // standard value type
        int?   Test2 = null;    // nullable value type         
        Object Test3 = null;    // reference type
     
        Response.Write("Test1: " + Test1.ToString() + "<br />");
        Response.Write("Test2: " + Test2.ToString() + "<br />");
        //Response.Write("Test3: " + Test3.ToString() + "<br />");
     
        // Output:
        //
        // Test1: 0   // correct
        // Test2:     // no exception, what? but it's null!
        //
        // If Test3 is allowed to run, we get:
        //   "Object reference not set to an instance of an object."

     It is odd that we can access Test2 when its value is null but we get no output string.

    Microsoft's web site correctly describes nullable types (http://msdn.microsoft.com/en-us/library/1t3y8s4s.aspx):

    "A nullable type can represent the correct range of values for its underlying value type, plus an additional null value."

    But most of us gloss over descriptions like that and until you see it demonstrated, you may not truly understand.

    I hope someone finds this useful, Steve Wellens.

  • Debugging a Deployed Site

    "It works on my machine."

    How many times have we heard that?  Getting something to work on other machines, after deployment, can be the final challenge in a successful project.  The worst case scenario is having to go to the problem machine (hopefully it isn't in the remote reaches of Siberia) installing the IDE, patches, source code, third party libraries, compiling and then debugging.  It's not a pleasant task.

    An often overlooked but useful tool is:  System.Diagnostics.Debug.WriteLine(...)

    Here are some sample lines in global.asax:

    <%@ Application Language="C#" %>
     
    <script RunAt="server">
     
        void Application_Start(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("****ApplicationStart");
        }
        void Application_BeginRequest(object sender, EventArgs e) 
        {
            System.Diagnostics.Debug.WriteLine("Application_BeginRequest");
        }
        void Application_EndRequest(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Application_EndRequest");
        }
        void Session_Start(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Session_Start");
        }

    Here is what the outputs look like in Visual Studio: Debug->Windows->Output. I turned off the Module Load messages to keep the output cleaner. An interesting thing to note is that the first Application_BeginRequest triggers the Session_Start event. Clicking a button a few times generates the other Application_BeginRequest events.

       

    So far so good, but how does this help debugging on a deployed machine? The answer is by using a free tool called DebugView from Sysinternals.  Microsoft has acquired Sysinternals so you can search Microsoft's web site to download it.

    Here is the same output captured by DebugView. The extra lines are caused by a Sound Card device driver on the host machine (the developers may have made a mistake).

       

    Notes:

    1)  Depending on the operating system, you may need to turn on global capturing of events in DebugView:

    Capture->Capture Global Win32

    2)  Debugging strings are turned off when compilation debug is false in web.config. To get the debug strings to work, debug must be "true"

    <compilation debug="true" strict="false" explicit="true">

    3)  If you run the site in Visual Studio, you will not see the strings in DebugView. 

    I hope you find this information useful.

    Steve Wellens