Categories
C# Software Engineering

Performance of C# vs VB.NET

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

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.

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.

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!

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.

Leave a Reply to Eric Maino Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.