Someone asked me a few days ago whether there were any major differences between C# and VB.NET performance, and my immediate reaction was “certainly not!”…. until I just came across this article on builder.com. In it, the author points out that for the number of program he tried, the C# version consistently generated fewer lines of IL code than its VB.NET partner.
Does anyone have any insight into this?
21 replies on “Performance of C# vs VB.NET”
You have got to be kidding! That article was written over two years ago, on a beta version, by someone who admits to being virtually clueless.
Any conclusions based on that article would be highly suspect.
I agree with the above comment. However, VB.Net has been known to generate less performant IL than c#, though the only reason I”m aware of revolve compiled-time options, such as integer overflow check (which vb.net has on by default, and c# doesn”t) and things like late binding (option strict).
Properly coded vb.net shouldn”t differ from properly coded c#, except that vb.net makes it easy to do a lot of improper things. In my opinion, the biggest difference between C# and VB.Net are the coders that use them.
Builder.com is an example of a good idea executed poorly. They try to be too many things to too many people and don”t get people who know what they”re doing to write anything. I went around with that author several times when I still visited the site and took him down. Back then, I wasn”t sure I knew what I was talking about either, so you know it was bad!
also remember this is *IL* NOT THE CODE!!
the jit may see the nop”s and drop them for example.
and the jit does optimise the IL when it emits x86 opcodes.
also I did not see: was this compiled for debug or release ??
and the vb version has a dim that the C# version does not so that adds a few bits to the code….
Ignoring the performance issue entirely for the moment, it is quite interesting to compare the IL generated by the VB.NET and C# compilers for what I would have thought is identical code.
Using the following code in C#, and writing an equivalent one in VB.NET
public void writeLine(string message)
{
if (myPrintFlag)
{
StringBuilder sb = new StringBuilder();
int i;
for (i=0; i<myIndent; i++)
{
sb.Append(" ");
}
sb.Append(message);
}
}
I got the following IL code (using ILdasm) for its VB.NET version
#######################
.method public instance void writeLine(string message) cil managed
{
// Code size 56 (0x38)
.maxstack 3
.locals init (int32 V_0,
class [mscorlib]System.Text.StringBuilder V_1,
int32 V_2)
IL_0000: ldarg.0
IL_0001: ldfld bool ILTestA.TestDemo::myPrintFlag
IL_0006: brfalse.s IL_0037
IL_0008: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_000d: stloc.1
IL_000e: ldc.i4.0
IL_000f: ldarg.0
IL_0010: ldfld int32 ILTestA.TestDemo::myIndent
IL_0015: ldc.i4.1
IL_0016: sub.ovf
IL_0017: stloc.2
IL_0018: stloc.0
IL_0019: br.s IL_002b
IL_001b: ldloc.1
IL_001c: ldstr " "
IL_0021: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0026: pop
IL_0027: ldloc.0
IL_0028: ldc.i4.1
IL_0029: add.ovf
IL_002a: stloc.0
IL_002b: ldloc.0
IL_002c: ldloc.2
IL_002d: ble.s IL_001b
IL_002f: ldloc.1
IL_0030: ldarg.1
IL_0031: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0036: pop
IL_0037: ret
} // end of method TestDemo::writeLine
#######################
vs its C# version:
#######################
.method public hidebysig instance void
writeLine(string message) cil managed
{
// Code size 52 (0x34)
.maxstack 2
.locals init (class [mscorlib]System.Text.StringBuilder V_0,
int32 V_1)
IL_0000: ldarg.0
IL_0001: ldfld bool ILTestB.TestDemo::myPrintFlag
IL_0006: brfalse.s IL_0033
IL_0008: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_000d: stloc.0
IL_000e: ldc.i4.0
IL_000f: stloc.1
IL_0010: br.s IL_0022
IL_0012: ldloc.0
IL_0013: ldstr " "
IL_0018: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_001d: pop
IL_001e: ldloc.1
IL_001f: ldc.i4.1
IL_0020: add
IL_0021: stloc.1
IL_0022: ldloc.1
IL_0023: ldarg.0
IL_0024: ldfld int32 ILTestB.TestDemo::myIndent
IL_0029: blt.s IL_0012
IL_002b: ldloc.0
IL_002c: ldarg.1
IL_002d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0032: pop
IL_0033: ret
} // end of method TestDemo::writeLine
The crucial thing there being that the C# version seems to be able to cope with a maximum stack size of 2, whilst the VB.NET version needs 3….
Addy: Consider the conclusion good. I just did the same test (with v1.1 of the compiliers/framework) and got the EXACT same results.
Karl: You are correct in the assumption about overflow checking generation different code in VB. If it is turned on you get the "SUB.OVF" and "ADD.OVF" instruction, and without it you just get the "SUB" and "ADD" instruction generated in the IL. No big difference. However, the one that does make the difference is the optimizations option. Enabling that gets rid of all the NOP calls (plus other things not evident in this example).
The big difference in the code has to do with the IL generated for the FOR loop. I would say the compilier is just not smart enough to use the the myIndent variable without making a copy of it locally (or the C# compilier makes a dumb move and does not make a location copy).
Denny: Debug or Release, it makes no difference. The IL code generated (in this case anyway) will not be different. Also, the extra "Dim" that VB does makes no difference either. That "Dim" was accounted for in the stack already. It”s the local copy of myIndent that is making the difference.
Clearly C# is a superior language! 😉
I think the article is badly written too. The VB source code isn”t even the as the c# source code. Its not much of difference, but if may make all the difference
Where he said:
Dim i as Integer
For i = 0 to indent – 1
He should write:
For i as Integer = 0 to indent -1.
THEN his VB and C# code would be equal and this would be a TRUE test.
The more I think about it, the more I come to the conclusion that the IL generated from the C# code is prone to error, and the VB code is not. Since the VB version creates a local copy of the myIndent variable, if the original is modified by another thread, the VB version would not be effected. However, since the C# version keeps getting the original value, it could be. In this example it does not matter, however, if the generated IL is any evidence of how things work, then the VB version is better.
Sam: Your correct in the line of code, but the IL is still the same.
Roy:
Are you sure? What happens when you look at the reverse:
instead of using the
for(int i,i++,i<indent)
you do
int i;
for(i,i++,i<indent)
Isn”t that the same thing too… but I would still expect this to compile differently…
This is a topic that interests me greatly and this semester in school I am performaning an independent study on such a topic. I will be looking into the IL code that in generated by C#, J#, VB.NET and possibly in other languages using other 3rd party compilers. I will be analyzing results in debug and release modes.
I will be posting most of my findings here which should be coming out in the next few weeks.
Sam: Positive. No matter where (locally) you declare the variable, it still gets added to the .locals IL instruction.
C#
.maxstack 2
.locals init ([0] class [mscorlib]System.Text.StringBuilder sb,
[1] int32 i)
VB
.locals init ([0] class [mscorlib]System.Text.StringBuilder sb,
[1] int32 i,
[2] int32 _Vb_t_i4_0)
You will notice the major difference here is the second int32 that VB is declaring (_Vb_t_i4_0) .. The is being used to keep a LOCAL copy of the myIndent value. The C# version does not do this.
It just jumped out at me. The VB IL is actually doing the following:
1. Loads the myIndent locally.
2. SUBTRACTS ONE
3. looping : while (i <= local)
C# IL does
1. looping: while (i < myIndent)
So, a FOR loop in VB and C# are NOT the same thing. To create the SAME IL, you must do the following:
VB:
If myPrintFlag Then
Dim sb As New StringBuilder
Dim i As Integer = 0
Do While (i < myIndent)
sb.Append(" "c)
i = i + 1
Loop
sb.Append(message)
myTextWriter.WriteLine(sb.ToString())
End If
C#
if (myPrintFlag){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < myIndent; i++){
sb.Append(” ”);
}
sb.Append(message);
myTextWriter.WriteLine(sb.ToString());
}
This will generate the exact same IL for both versions of the source (minus the order of the variables in the .locals section).
Solved!
Of Course!!! Excellent thinking Roy!
Suddenly it all becomes clear! Thanks very much Roy (and everyone else who came back with comments) – That”s quite an interesting conclusion in terms of language design.
For a few more tidbits, check out:
Paul Vick explains what ”enable optimizations” actually does…
http://addressof.com/blog/posts/357.aspx
VB.NET Performance Optimizations
http://addressof.com/blog/posts/339.aspx
VB.NET Performance Related Compiler Options
http://addressof.com/blog/posts/340.aspx
More on Remove Integer Overflow Checks
http://addressof.com/blog/posts/341.aspx
yeah it”s true that C# IL is small in size in comparison to VB.NET IL , and even if the VB.NET IL contains nop opcodes then even the their translation to nothing while converting to machine code will consume time and so the performance is affected.
nop are only generated in VB code compiled under DEBUG mode. This is because VB lets you break execution on a no-operating line of code (like an End If or other End Block) so it puts nops in so that there are actual instruction lines to break on. In the Release Build there is no breaking and there should be no nop codes. These rumors and half-assed assesments were done with either crap understanding of VB.NET or on BETAs.
Equivalent code written in the languages compile with equivalent compiler options should produce equivalent IL (though something that look equivalent aren”t). If anything the VB compiler would be better than the C# since the C# compiler is only 1.1 versions old.