C# Performance
-
I'm afraid that's not true, since the resulting IL is identical. Regards, mav
-
Out of sheer curiosity I have a quick question relating to C# performance. I have the two following methods:
public static string VerboseProgrammingMethod()
{
System.Guid g = Guid.NewGuid();
string s = g.ToString();
string f = s.Substring(0,4);return f;
}public static string OneLineProgrammingMethod()
{
return Guid.NewGuid().ToString().Substring(0, 4);
}As you can see both of them will generate exactly the same output as they essentially perform the same functions. The question is is one of them quicker/less memory intensive than the other? I am under the impression that it doesnt make any difference whatsoever, but I would be grateful if somebody could confirm this
Like the others say, both are the same. #1 is must easier to debug though, and should allways be used (for clarity too). Make a habit of it. We are proffesionals, not script kiddie/wannabe hackers :) NOTE: That saying, I still take the 'short' route sometimes :-O xacc.ide-0.1.1.4 - now with LSharp integration and scripting :)
-
Like the others say, both are the same. #1 is must easier to debug though, and should allways be used (for clarity too). Make a habit of it. We are proffesionals, not script kiddie/wannabe hackers :) NOTE: That saying, I still take the 'short' route sometimes :-O xacc.ide-0.1.1.4 - now with LSharp integration and scripting :)
I totally agree with your professionals vs script kiddie comment, but on occasion it is sometimes necessary to make sacrifices for performance. As suggested by Jamie4C I created the following class library and ran it through ILDasm
public class Class1
{
public static string VerboseProgrammingMethod()
{
System.Guid g = Guid.NewGuid();
string s = g.ToString();
string f = s.Substring(0,4);return f;
}public static string OneLineProgrammingMethod()
{
return Guid.NewGuid().ToString().Substring(0, 4);
}
}
}Now in theory the IL code for these two functions should be exactly the same, interestingly there are differences. I am no IL expert so this might be a red herring, but here is the IL code:
.method public hidebysig static string VerboseProgrammingMethod() cil managed
{
// Code size 29 (0x1d)
.maxstack 3
.locals init ([0] valuetype [mscorlib]System.Guid g,
[1] string s,
[2] string f,
[3] string CS$00000003$00000000)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s g
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldc.i4.0
IL_0010: ldc.i4.4
IL_0011: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0016: stloc.2
IL_0017: ldloc.2
IL_0018: stloc.3
IL_0019: br.s IL_001b
IL_001b: ldloc.3
IL_001c: ret
} // end of method Class1::VerboseProgrammingMethod.method public hidebysig static string OneLineProgrammingMethod() cil managed
{
// Code size 25 (0x19)
.maxstack 3
.locals init ([0] string CS$00000003$00000000,
[1] valuetype [mscorlib]System.Guid CS$00000002$00000001)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.1
IL_0006: ldloca.s CS$00000002$00000001
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: ldc.i4.0
IL_000e: ldc.i4.4
IL_000f: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0014: stloc.0
IL_0015: br.s IL_0017
IL_0017: ldloc.0
IL_0018: ret
} // end of method Class1::OneLineProgrammingMethod -
I totally agree with your professionals vs script kiddie comment, but on occasion it is sometimes necessary to make sacrifices for performance. As suggested by Jamie4C I created the following class library and ran it through ILDasm
public class Class1
{
public static string VerboseProgrammingMethod()
{
System.Guid g = Guid.NewGuid();
string s = g.ToString();
string f = s.Substring(0,4);return f;
}public static string OneLineProgrammingMethod()
{
return Guid.NewGuid().ToString().Substring(0, 4);
}
}
}Now in theory the IL code for these two functions should be exactly the same, interestingly there are differences. I am no IL expert so this might be a red herring, but here is the IL code:
.method public hidebysig static string VerboseProgrammingMethod() cil managed
{
// Code size 29 (0x1d)
.maxstack 3
.locals init ([0] valuetype [mscorlib]System.Guid g,
[1] string s,
[2] string f,
[3] string CS$00000003$00000000)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s g
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldc.i4.0
IL_0010: ldc.i4.4
IL_0011: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0016: stloc.2
IL_0017: ldloc.2
IL_0018: stloc.3
IL_0019: br.s IL_001b
IL_001b: ldloc.3
IL_001c: ret
} // end of method Class1::VerboseProgrammingMethod.method public hidebysig static string OneLineProgrammingMethod() cil managed
{
// Code size 25 (0x19)
.maxstack 3
.locals init ([0] string CS$00000003$00000000,
[1] valuetype [mscorlib]System.Guid CS$00000002$00000001)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.1
IL_0006: ldloca.s CS$00000002$00000001
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: ldc.i4.0
IL_000e: ldc.i4.4
IL_000f: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0014: stloc.0
IL_0015: br.s IL_0017
IL_0017: ldloc.0
IL_0018: ret
} // end of method Class1::OneLineProgrammingMethodIs this the debug or the release build? There shouldn't be any differences in the release build. The debug build will contain some differences which is why the first version of the code is easier to debug (the compiler doesn't perform so many optimisations in debug builds) ColinMackay.net "Man who stand on hill with mouth open will wait long time for roast duck to drop in." -- Confucius "If a man empties his purse into his head, no man can take it away from him, for an investment in knowledge pays the best interest." -- Joseph E. O'Donnell
-
Is this the debug or the release build? There shouldn't be any differences in the release build. The debug build will contain some differences which is why the first version of the code is easier to debug (the compiler doesn't perform so many optimisations in debug builds) ColinMackay.net "Man who stand on hill with mouth open will wait long time for roast duck to drop in." -- Confucius "If a man empties his purse into his head, no man can take it away from him, for an investment in knowledge pays the best interest." -- Joseph E. O'Donnell
The above IL listings were from a debug build, but there are still differences in a release build:
.method public hidebysig static string VerboseProgrammingMethod() cil managed
{
// Code size 25 (0x19)
.maxstack 3
.locals init (valuetype [mscorlib]System.Guid V_0,
string V_1,
string V_2)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldc.i4.0
IL_0010: ldc.i4.4
IL_0011: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0016: stloc.2
IL_0017: ldloc.2
IL_0018: ret
} // end of method Class1::VerboseProgrammingMethod.method public hidebysig static string OneLineProgrammingMethod() cil managed
{
// Code size 21 (0x15)
.maxstack 3
.locals init (valuetype [mscorlib]System.Guid V_0)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: ldc.i4.0
IL_000e: ldc.i4.4
IL_000f: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0014: ret
} // end of method Class1::OneLineProgrammingMethod-- modified at 7:31 Monday 9th January, 2006
-
The above IL listings were from a debug build, but there are still differences in a release build:
.method public hidebysig static string VerboseProgrammingMethod() cil managed
{
// Code size 25 (0x19)
.maxstack 3
.locals init (valuetype [mscorlib]System.Guid V_0,
string V_1,
string V_2)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldc.i4.0
IL_0010: ldc.i4.4
IL_0011: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0016: stloc.2
IL_0017: ldloc.2
IL_0018: ret
} // end of method Class1::VerboseProgrammingMethod.method public hidebysig static string OneLineProgrammingMethod() cil managed
{
// Code size 21 (0x15)
.maxstack 3
.locals init (valuetype [mscorlib]System.Guid V_0)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: ldc.i4.0
IL_000e: ldc.i4.4
IL_000f: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0014: ret
} // end of method Class1::OneLineProgrammingMethod-- modified at 7:31 Monday 9th January, 2006
After a lot of pain I managed to get the Windows Debugger hooked up to a process containing these two methods (using the MethodImpl attribute to suppress inlining), and dump the JITted code. Here's the result: OneLineProgrammingMethod:
sub esp,0x10
xor eax,eax
mov [esp],eax
mov [esp+0x4],eax
mov [esp+0x8],eax
mov [esp+0xc],eax
lea ecx,[esp]
call mscorlib_79990000+0x35d28 (799c5d28)push 0x0
lea ecx,[esp+0x4]
mov edx,[01c415d0]
call mscorlib_79990000+0x35de0 (799c5de0)
push 0x4
mov ecx,eax
xor edx,edx
cmp [ecx],ecxcall mscorlib_79990000+0x6b20 (79996b20)
add esp,0x10
retVerboseProgrammingMethod:
sub esp,0x10
xor eax,eax
mov [esp],eax
mov [esp+0x4],eax
mov [esp+0x8],eax
mov [esp+0xc],eax
lea ecx,[esp]
call dword ptr [mscorlib_79990000+0x234a08 (79bc4a08)]push 0x0
lea ecx,[esp+0x4]
mov edx,[01c415d0]
call dword ptr [mscorlib_79990000+0x234a28 (79bc4a28)]
push 0x4
mov ecx,eax
xor edx,edx
cmp [ecx],ecxcall mscorlib_79990000+0x6b20 (79996b20)
add esp,0x10
retThe only difference is that for some reason the JIT compiler has decided to call through a table for the verbose method - the functions called are exactly the same. I'm actually surprised - I was expecting the IL to be different since the C# compiler does very little optimisation but I wasn't expecting a difference in the JITted code. Nevertheless you should normally forget about micro-optimisations like this and concentrate on the areas that will have the biggest impact. Stability. What an interesting concept. -- Chris Maunder
-
The above IL listings were from a debug build, but there are still differences in a release build:
.method public hidebysig static string VerboseProgrammingMethod() cil managed
{
// Code size 25 (0x19)
.maxstack 3
.locals init (valuetype [mscorlib]System.Guid V_0,
string V_1,
string V_2)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldc.i4.0
IL_0010: ldc.i4.4
IL_0011: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0016: stloc.2
IL_0017: ldloc.2
IL_0018: ret
} // end of method Class1::VerboseProgrammingMethod.method public hidebysig static string OneLineProgrammingMethod() cil managed
{
// Code size 21 (0x15)
.maxstack 3
.locals init (valuetype [mscorlib]System.Guid V_0)
IL_0000: call valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call instance string [mscorlib]System.Guid::ToString()
IL_000d: ldc.i4.0
IL_000e: ldc.i4.4
IL_000f: callvirt instance string [mscorlib]System.String::Substring(int32,
int32)
IL_0014: ret
} // end of method Class1::OneLineProgrammingMethod-- modified at 7:31 Monday 9th January, 2006
-
After a lot of pain I managed to get the Windows Debugger hooked up to a process containing these two methods (using the MethodImpl attribute to suppress inlining), and dump the JITted code. Here's the result: OneLineProgrammingMethod:
sub esp,0x10
xor eax,eax
mov [esp],eax
mov [esp+0x4],eax
mov [esp+0x8],eax
mov [esp+0xc],eax
lea ecx,[esp]
call mscorlib_79990000+0x35d28 (799c5d28)push 0x0
lea ecx,[esp+0x4]
mov edx,[01c415d0]
call mscorlib_79990000+0x35de0 (799c5de0)
push 0x4
mov ecx,eax
xor edx,edx
cmp [ecx],ecxcall mscorlib_79990000+0x6b20 (79996b20)
add esp,0x10
retVerboseProgrammingMethod:
sub esp,0x10
xor eax,eax
mov [esp],eax
mov [esp+0x4],eax
mov [esp+0x8],eax
mov [esp+0xc],eax
lea ecx,[esp]
call dword ptr [mscorlib_79990000+0x234a08 (79bc4a08)]push 0x0
lea ecx,[esp+0x4]
mov edx,[01c415d0]
call dword ptr [mscorlib_79990000+0x234a28 (79bc4a28)]
push 0x4
mov ecx,eax
xor edx,edx
cmp [ecx],ecxcall mscorlib_79990000+0x6b20 (79996b20)
add esp,0x10
retThe only difference is that for some reason the JIT compiler has decided to call through a table for the verbose method - the functions called are exactly the same. I'm actually surprised - I was expecting the IL to be different since the C# compiler does very little optimisation but I wasn't expecting a difference in the JITted code. Nevertheless you should normally forget about micro-optimisations like this and concentrate on the areas that will have the biggest impact. Stability. What an interesting concept. -- Chris Maunder
Mike Dimmick wrote:
Nevertheless you should normally forget about micro-optimisations like this and concentrate on the areas that will have the biggest impact.
In this example, I agree it is an "optimisation" that will save nano seconds if that. That being said, if the methods were dealing with much larger objects it could potentially be something worth considering.
-
The two strings are created in both codes. The only difference is that the references to the strings are declared as variables in one code, while in the other code the compiler will create the references itself to handle the strings. --- b { font-weight: normal; }
-
Mike Dimmick wrote:
Nevertheless you should normally forget about micro-optimisations like this and concentrate on the areas that will have the biggest impact.
In this example, I agree it is an "optimisation" that will save nano seconds if that. That being said, if the methods were dealing with much larger objects it could potentially be something worth considering.
As the only difference in coding is to keep references to the objects created, it doesn't matter how large the objects are. The size of a reference is always the same. When optimizing code it's very useful to know what the code really does, in order to find out what code is worth optimizing, and how. Example: int a = 42; string y = a + "," + a; That looks simple, but can be written much more efficient in this more lengthy way: int a = 42; string x = a.ToString(); string y = x + "," + x; The first code will be executed something like: int a = 42; string y = string.Concat(Number.FormatInt32((int)new Object(a), null, NumberFormatInfo.GetInstance(null)), (string)(object)",", Number.FormatInt32((int)new Object(a), null, NumberFormatInfo.GetInstance(null))); while the second code will be executed as: int a = 42; string x = Number.FormatInt32(a, null, NumberFormatInfo.GetInstance(null)); string y = string.Concat(x, ",", x); --- b { font-weight: normal; }
-
Probably a half second faster over the span of 1 million calls to it. But, of course, your milage may vary depending on your machine and how you drive it. RageInTheMachine9532 "...a pungent, ghastly, stinky piece of cheese!" -- The Roaming Gnome