Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Analyze IL and say hello to your performance

0.00/5 (No votes)
3 Sep 2004 1  
Avoid unnecessary variable declaration and redundant instructions. Analyzing and understanding the compiler and generated IL instructions in a simple example, saving your performance.

Introduction

In this article, I�ll try to demonstrate a theory which I have since some time ago, and that some people to whom I talk sometimes ignore. The �please-avoid-too-many-and-unnecessary-private �variables-when-you-really-don�t-need it!� theory, or friendly name �The-Not-Clearer-Code� theory.

First of all, I'm not English natural and I�m not an expert in .NET, so, please be gentle with me :)

The problem

Imagine the following code:

int result = Sum(value1,value2);
if(result>0)
    return true;
else
    return false;

Most of you already know what I mean. Why the hell do we need result. Yes, why do we need it? We don�t. But some guys insist on doing this. Usually, the justification is �It�s clearer to do an if instruction next, and more readable�. I know� you are not one of them ;). But for those who are not reading this article, I�m going to definitely explain why you should avoid unnecessary code, and why you should turn that on a �return Sum(value1, value0)>0;� or even better, on a �return value1+value0>0;�.

Of course, this article is not about the �exact� code as shown, but about avoiding unnecessary stack memory use, and really understanding why.

So what are really the reasons?

First of all, no, sorry, but it�s not clearer! (Anybody disagrees?). I cannot understand why someone will find this clearer. If someone who is not reading this article thinks that, I suggest that also refactor the variable name result to resultOfSomeSomeBetweenTwoValuesThatImGonnaSeeIfItIsGreatherThanZero. That is clearer.

But after all, does somebody know what this is going to result in your code? Of course, you know: unnecessary stack memory consumption and redundant runtime instructions.

Please consider the following console app:

using System;

namespace TestStack
{
 class Class1
 {

   private static int Sum(int value1, int value2)
   {
     return value1 + value2;
   }

   private static bool AnnoyingIsResultGreaterThanZero 
                                (int value1, int value2 )
   {
     int result = Sum(value1,value2);
     if (result>0)
       return true;
     else
       return false;
   }

   private static bool IsResultGreaterThanZero(int value1, int value2 )
   {
     return Sum(value1,value2)>0;
   }

   [STAThread]
   static void Main(string[] args)
   {

     const int ITERATIONS = 2147483647;
     Console.WriteLine("Doing {0} iterations...",ITERATIONS);
     System.DateTime start ;
     bool result;

     //Warm up

     result  = IsResultGreaterThanZero(1,1);
     result = AnnoyingIsResultGreaterThanZero(1,1);
     result = (Sum(1,1)>0);
     result = (1+1>0);

     start = System.DateTime.UtcNow;
     Console.WriteLine("Calling the " + 
        "AnnoyingIsResultGreaterThanZero method...");

     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result = AnnoyingIsResultGreaterThanZero(1,1);
     }
     Console.WriteLine("Total time taken with " + 
         "AnnoyingIsResultGreaterThanZero method : " + 
         (System.DateTime.UtcNow - start).ToString());

     start = System.DateTime.UtcNow;
     Console.WriteLine("Calling the IsResultGreaterThanZero method...");
     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result  = IsResultGreaterThanZero(1,1);
     }
     Console.WriteLine("Total time taken with " + 
            "IsResultGreaterThanZero method : " + 
            (System.DateTime.UtcNow - start).ToString());

     start = System.DateTime.UtcNow;
     Console.WriteLine("Directly calling the Sum method ...");
     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result = (Sum(1,1)>0);
     }
     Console.WriteLine("Total time taken with Sum method: " + 
                (System.DateTime.UtcNow - start).ToString());

     start = System.DateTime.UtcNow;
     Console.WriteLine("Directly making the sum ...");
     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result = (1+1>0);
     }
     Console.WriteLine("Total time taken with directly sum : " + 
                   (System.DateTime.UtcNow - start).ToString());

     Console.ReadLine();
   }
 }
}

We have some basic members here:

  • Sum returns the sum between two integers,
  • AnnoyingIsResultGreaterThanZero and IsResultGreaterThanZero return true if the result between two values is greater than zero, but AnnoyingIsResultGreaterThanZero will do it in a redundant way;
  • Main starts the app and runs some perf tests, writing the time taken to the console;

Both AnnoyingIsResultGreaterThanZero and IsResultGreaterThanZero produce same results. Although, both are radically different in the generated IL.

Let�s see the IL generated by IsResultGreaterThanZero. To see the IL, you can use reflector or ILDASM provided in the SDK. (Tip: with reflector, you can see more details about the generated IL when the mouse is over the IL instruction.)

.method private hidebysig static bool 
    IsResultGreaterThanZero(int32 value1, int32 value2) cil managed
{
      // Code Size: 11 byte(s)

      .maxstack 2
      L_0000: ldarg.0 
      L_0001: ldarg.1 
      L_0002: call int32 TestStack.Class1::Sum(int32, int32)
      L_0007: ldc.i4.0 
      L_0008: cgt 
      L_000a: ret 
}

This seems very simple IL. L_0000 and L_00001 load arguments to the evaluation stack. Next, we call Sum, load a 0 (the value to compare), and compare the value returned by Sum and the value just pushed to stack (0), and exits the sub.

When analyzing the IL generated by AnnoyingIsResultGreaterThanZero, we can see the following:

.method private hidebysig static bool 
    AnnoyingIsResultGreaterThanZero(int32 value1, int32 value2) cil managed
{
      // Code Size: 28 byte(s)

      .maxstack 4
      .locals (
            int32 V_0,
            bool V_1)
      L_0000: ldarg.0 
      L_0001: ldarg.1 
      L_0002: call int32 TestStack.Class1::Sum(int32, int32)
      L_0007: stloc.0 
      L_0008: ldloc.0 
      L_0009: ldc.i4.0 
      L_000a: ble.s L_0013
      L_000c: ldc.i4.1 
      L_000d: stloc.1 
      L_000e: br L_001a
      L_0013: ldc.i4.0 
      L_0014: stloc.1 
      L_0015: br L_001a
      L_001a: ldloc.1 
      L_001b: ret 
}

As you can see, this is radically different. First of all, and as you can see, with line L_000a and L_000d, we�ll need more memory to store the result of �if� evaluation. Second, the runtime has 10 more instructions to evaluate.

Running the app and seeing results

My test environment is a PIV 3.06Mhz HT, 1GB RAM, running .NET 1.1. The app was compiled in release without /optimize switch. The results are:

Doing 2147483647 iterations...
Calling the AnnoyingIsResultGreaterThanZero method...
Total time taken with AnnoyingIsResultGreaterThanZero method : 00:00:11.5937500
Calling the IsResultGreaterThanZero method...
Total time taken with IsResultGreaterThanZero method : 00:00:01.5468750
Directly calling the Sum method ...
Total time taken with Sum method: 00:00:01.5312500
Directly making the sum ...
Total time taken with directly sum : 00:00:01.7187500

I'm making 2147483647 iterations. I know it's a lot, but with this test case, only with a lot of iterations we get some conclusions. As you can see, we're talking of 10 times more.

Even using /optimize switch, we still get slower results as you may see:

Doing 2147483647 iterations...
Calling the 
AnnoyingIsResultGreaterThanZero method...
Total time taken with AnnoyingIsResultGreaterThanZero method : 00:00:02.2343750
Calling the IsResultGreaterThanZero method...
Total time taken with IsResultGreaterThanZero method : 00:00:01.4843750
Directly calling the Sum method ...
Total time taken with Sum method: 00:00:01.5000000
Directly making the sum ...
Total time taken with directly sum : 00:00:01.5000000

Conclusion

This is a very risky subject for me, since, as I told in previous article comments, I'm not an expert in JIT. In the previous article version, I committed some mistakes exactly because of that, and maybe I'm having worst mistakes right now . . . :) Please feel free to comment and help to take conclusions on this subject.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here