none
Optimizing temporary/intermediate variables. RRS feed

  • Question

  • In our C++ code base, our coding rules require that we separate and identify otherwise arbitrary values with temporaries: please note, this sample code is wholly artificial, entirely pointless/meaningless and was created solely for the purpose of generating some IL code a little further down.

    Our C++ code policy encourages breaking down of complex calculations into named intermediate variables.

    I realize that - in the following examples, there's no reason not to make MeaningOfLife a private/internal member variable; however our C++ code policy discourages this for single-use values because they can easily become separated from the code to which they are relevant.

     

    Also, I realize that, were it a member, we could similarly initialize "lifeRoot" as a static member or something, given that the source value is static.

    However: The code is a reduction/simplification of the recurring concept of intermediate steps of calculation where, for code legibility, you want to temporarily assign a calculated value a name in source only as with the "lifeRoot" 

     

    // C++ code
    
    float SumRoots(int n1, int n2)
    {
     // Local intermediates: that is, the value won't
     // change for the lifetime of any given call of this
     // function, so the compiler can effectively optimize
     // them out.
     // NOTE: This is different than C#s definition of 'const'.
     const float root1 = sqrt(n1) ;
     const float root2 = sqrt(n2) ;
    
     // Our one and only references to root1 and root2.
     const float rootSum = root1 + root2 ;
    
     return rootSum ;
    }
    

    Converting this vaguely to C#:

    public float SumRoots(int n1, int n2)
    {
      double root1 = Math.Sqrt(n1) ;
      double root2 = Math.Sqrt(n2) ;
    
      double rootSum = root1 + root2 ;
    
      return rootSum ;
    }
    
    

    What surprised me here was the resulting IL code produced with optimization enabled [and I mean, Build -> Optimize code checked, not just assuming it's optimizing in Release mode :)]

    // Code size    22 (0x16)
     .maxstack 2
     .locals init ([0] float64 root1,
          [1] float64 root2,
          [2] float64 rootSum)
     IL_0000: ldarg.0
     IL_0001: conv.r8
     IL_0002: call    float64 [mscorlib]System.Math::Sqrt(float64)
     IL_0007: stloc.0
     IL_0008: ldarg.1
     IL_0009: conv.r8
     IL_000a: call    float64 [mscorlib]System.Math::Sqrt(float64)
     IL_000f: stloc.1
     IL_0010: ldloc.0
     IL_0011: ldloc.1
     IL_0012: add
     IL_0013: stloc.2
     IL_0014: ldloc.2
     IL_0015: ret

    I'd expected (having been spoiled by C++ optimizers etc) something more akin to:

     // Code size    16 (0x10)
     .maxstack 8
     IL_0000: ldarg.0
     IL_0001: conv.r8
     IL_0002: call    float64 [mscorlib]System.Math::Sqrt(float64)
     IL_0007: ldarg.1
     IL_0008: conv.r8
     IL_0009: call    float64 [mscorlib]System.Math::Sqrt(float64)
     IL_000e: add
     IL_000f: ret
    

    But the C# compiler's optimizer doesn't seem to perform the same kind of local elimination that the C/C++/CLI-C++ compilers do. While I appreciate it might be done by the JIT instead, this seems to unduly bloat the size of the resulting assemblies.

    I'm wondering:

    a. Is there a way to provide this kind of hint to the C# compiler?

    b. Is the C#-generated variant better for parallelization?

     

    Monday, February 7, 2011 10:02 PM

Answers

  •  

    I'm wondering:

    a. Is there a way to provide this kind of hint to the C# compiler?

    b. Is the C#-generated variant better for parallelization?

     

    a. No

    b. No

     

    This type of optimization is left up to the JIT in C#.  In this case, this is one place where the C# compilers are just behind the C++ compilers.  That being said, these optimizations are rarely done at the compiler level in .NET, and instead pushed to the JIT level.

     

    However, I wouldn't worry about it in general - forget the micro-optimizations, and focus on the large scale algorithmic optimizations you can achieve (which are much more effective, and often much easier to create in C#/.NET due to the framework), until you find a "real" (ie: measured) performance problem.  Then you can go eliminate the bottlenecks...

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    • Proposed as answer by James Michael Hare Monday, February 7, 2011 10:27 PM
    • Marked as answer by Paul Zhou Friday, February 18, 2011 5:35 AM
    Monday, February 7, 2011 10:22 PM
    Moderator