Reputation: 563
Will the .NET compiler optimize this:
public MyObject GetNewObject()
{
var newCurrentObject = myObjectFactory.CreateNew(
DateTime.Now,
"Frank",
41,
secretPassword);
return newCurrentObject;
}
to execute with the same number of instructions/memory as this:
public MyObject GetNewObject()
{
return myObjectFactory.CreateNew(
DateTime.Now,
"Frank",
41,
secretPassword);
}
Or will the local variable result in extra time and memory being spent to create a reference (newObject) to the MyObject only destroy it the next line once it's out of scope.
I ask because, performance all the same, I find the first to be more readable as a local variable name can often give the next developer some context as to what we're doing here.
Upvotes: 6
Views: 1830
Reputation: 40838
In general, when you turn optimizations on in the C# compiler it attempts to minimize the number of temporary/local variable storage slots used in methods. Therefore, you should not concern yourself with trying to minimize the number of variables because the compiler will do it for you. Without optimizations, there will be some differences because the goal there is to enhance the debugging experience and preserve as much information as possible.
In this particular case, the exact same IL is emitted for both methods when optimizations are enabled. Here is a complete snippet including a value type and reference type for comparison:
using System;
public class C {
private static MyStruct NewStructLocal()
{
var s = new MyStruct();
return s;
}
private static MyStruct NewStructReturn()
{
return new MyStruct();
}
private static MyClass NewClassLocal()
{
var s = new MyClass();
return s;
}
private static MyClass NewClassReturn()
{
return new MyClass();
}
}
public struct MyStruct
{
public int I;
}
public class MyClass
{
public int I;
}
And the emitted IL is:
.method private hidebysig static
valuetype MyStruct NewStructLocal () cil managed
{
// Method begins at RVA 0x2054
// Code size 10 (0xa)
.maxstack 1
.locals init (
[0] valuetype MyStruct
)
IL_0000: ldloca.s 0
IL_0002: initobj MyStruct
IL_0008: ldloc.0
IL_0009: ret
} // end of method C::NewStructLocal
.method private hidebysig static
valuetype MyStruct NewStructReturn () cil managed
{
// Method begins at RVA 0x206c
// Code size 10 (0xa)
.maxstack 1
.locals init (
[0] valuetype MyStruct
)
IL_0000: ldloca.s 0
IL_0002: initobj MyStruct
IL_0008: ldloc.0
IL_0009: ret
} // end of method C::NewStructReturn
.method private hidebysig static
class MyClass NewClassLocal () cil managed
{
// Method begins at RVA 0x2082
// Code size 6 (0x6)
.maxstack 8
IL_0000: newobj instance void MyClass::.ctor()
IL_0005: ret
} // end of method C::NewClassLocal
.method private hidebysig static
class MyClass NewClassReturn () cil managed
{
// Method begins at RVA 0x2082
// Code size 6 (0x6)
.maxstack 8
IL_0000: newobj instance void MyClass::.ctor()
IL_0005: ret
} // end of method C::NewClassReturn
Thus as far as performance is concerned, they will be identical.
In the case of reference types, the object is created, a reference is put on the top of the evaluation stack, and immediately returned from there.
In the case of value types, a local storage location is declared, the value in the storage location is initialized, the value is loaded again, and then returned. This is pretty typical for value types that aren't primitive types.
A more general item to think about is that in a method of any significant complexity the compiler generates a lot of unnamed temporaries that do not have names accessible to the programmer. In terms of performance, worrying about the how many named local variables you can see in the code is spending your time worrying about the wrong thing.
Note
The question has changed somewhat since I first wrote this answer, however I think all of this analysis still stands. In the specific case, given you get the exact same IL in both methods when compiling with optimizations. The local variable has no effect on the emitted IL. Now if you were to add more local variables for the other arguments, you would have different IL and would need to inspect the disassembly to determine the full extent of the differences.
Upvotes: 4
Reputation: 3508
Objects are returned by reference. The reference is created in both cases and it is on the top of the stack. It is one reference and the reference is then returned, so it is the same. The object is not moved to another place in the heap, thus the reference cannot change. You would probably have to clone the object and then return the reference to the cloned object. Then, you would have an extra reference and also an extra space allocated on the heap.
The only issues would arise if instead of MyObject you used a primitive type, e.g. int, but the return type would still be Object:
public object BoxingOccurs()
{
int i = 5;
return i; // or shortly return 5;
}
The primitive type would then be boxed, which means that a value type is converted to an object type. The primitive type is wrapped and moved to the managed heap. More about boxing and unboxing can be found here.
Upvotes: 3
Reputation: 171236
Assuming MyObject
is a reference type the same x86 will be generated for both cases. The JIT is quite competent at optimizing away scalar temporaries and assignments. This is one of the most basic optimizations there is. Pretty much any optimizer uses SSA form internally and this optimization almost falls out of SSA form.
Assuming MyObject
is a struct
: I have extensively tested the .NET 4.5 JIT and the new RyuJIT for struct optimizations. The .NET JITs generally do not optimize structs assignments and (local) variables. Code is literally translated except for one minor case that does not apply here. Expect totally literal machine code. Even if you say a = a;
or a.x = 1; a.x = 1;
you get exactly that as machine code. Send a mail to the team if structs are important to you. Now is still time to make the changes.
Upvotes: 6
Reputation: 13217
newObject
is a variable, not a reference (the variable contains a reference to an object). I've tried the code (in release mode), and the compiler doesn't optimize the code like this. I am not sure, but it may be even disallowed by the specification, because variables are part of reflection, and this may affect expected behavior. However, it doesn't mean the code won't be run that way due to the JITter, that might optimize it.
Upvotes: 0