Declaring loop variables in Method Scope versus Loop Scope in C#...

The program I used to replicate this behavior is shown below.  I had to put the WriteLine's in, because the /optimize+ versions were omitting the n = i statement, since n was never used.  While I found that portion of the code extremely smart, I found that the variable re-usage of all forms of compilation were extremely lacking.  For example, the IL output from each function is as follows:

ShowBlockScopedVariablesInILAsMethodScoped, this method demonstrates that loop scoped variables within a C# application, actually manifest at the method level rater than somehow manifesting at the loop scoped level.  This makes perfect sense so that upon entry into the method, the stack is set to the appropriate size as needed by the running code.  2 stack variables are allocated in this method as expected.

ShowMultipleBlockScopedVariablesInILAsMultipleMethodScopedVariables, this method demonstrates that loop scoped variables within a C# application don't get re-used at all.  Normally, you would expect any stack variable used in a loop, to become available for use by the program in another loop once the current loop has been exited.  However, this isn't the case since there are 6 stack variables allocated 2 for each loop with one variable mapping to i in each loop and the other being n.

ShowMethodScopedVariablesReUsed, this method demonstrates that you can reduce your stack footprint by not using loop scoped variables and simply declaring method scoped variables and re-using them manually.  This is somewhat counter-intuitive to me, since the language could optimize the loop scoped variables and re-use them, but doesn't. 

Long story short, try not to use locally scoped variables for loop constructs unless you only have one loop in your code.  Try to re-use variables where appropriate.  This is the lesson I've learned.  Maybe I'm trying to be too protective of the generated IL and the JIT is doing something behind the scenes.  I notice that the Partition II document shipped with the Framework SDK demonstrates that ILAsm allows locally scoped variables within nested blocks to share the same location as a method scoped variable.  You can read about this in 14.4.1.3.  I even tried to create some IL that did this and compile it out:

    .locals init (
             int32 V_0,
             int32 V_1,
             [0] int32 V_2,
             [1] int32 V_3,
             [0] int32 V_4,
             [1] int32 V_5)

The above compiled just fine under ILAsm.  However, it did wind up producing IL that simply removed the remaining 4 variables and made sure the references pointed to V_0 through V_1.  However, the really funny thing is that ldloc.{digit} and stloc.{digit} are used everywhere they would have normally been used, but the final two references that mapped V_4/V_5 to V_0/V_1 were listed as stloc.s/ldloc.s.  I'm not sure there is any speed increase over any of the different versions of stloc/ldloc, but if there was, even the ILAsm compiler is lacking in performance optimizations.

Well, I'm done with this topic for the evening.  I probably won't change very much about how I code based on any of this information.  It may force me to use method level variables in places where I have lots of loop structures, but probably not.

using System;

public class LocalityOfVariables {
    private static void Main(string[] args) {
    }
   
    private static void ShowBlockScopedVariablesInILAsMethodScoped() {
        for (int i = 0; i < 10; ++i) {
            int n = i;
            Console.WriteLine(n);
        }
    }
   
    private static void ShowMultipleBlockScopedVariablesInILAsMultipleMethodScopedVariables() {
        for (int i = 0; i < 10; ++i) {
            int n = i;
            Console.WriteLine(n);
        }
        for (int i = 0; i < 10; ++i) {
            int n = i;
            Console.WriteLine(n);
        }
        for (int i = 0; i < 10; ++i) {
            int n = i;
            Console.WriteLine(n);
        }
    }
   
    private static void ShowMethodScopedVariablesReUsed() {
        int i, n;
   
        for (i = 0; i < 10; ++i) {
            n = i;
            Console.WriteLine(n);
        }
        for (i = 0; i < 10; ++i) {
            n = i;
            Console.WriteLine(n);
        }
        for (i = 0; i < 10; ++i) {
            n = i;
            Console.WriteLine(n);
        }
    }
}

 

Published Monday, February 16, 2004 12:59 AM by Justin Rogers

Comments

No Comments