Posts (Rebuttal) Reason #458 Why Visual Basic.NET Sucks
Post
Cancel

(Rebuttal) Reason #458 Why Visual Basic.NET Sucks

I can't help it... this crap is so wrong that it *MUST* be discussed.  It's been picked up my many people in the blogsphere and used as “reference“ material in forums.  It's wrong, wrong, wrong, WRONG!  You'd think after almost 5 months of being posted people would check their facts.  So since others apparently don't wish to do so, here goes.

Where should I start?  Well, let's ignore the “performance” portion of this and break down a few other things before I get there.  Let's look at some of his comments:

Supposedly he decided to do a bit of IL spelunking and discovered that VB does indeed treat the = operator a bit differently than C# does.  In fact, VB does a lot more and it's wrapped up within a call to Microsoft.VisualBasic.CompilerServices.StringType.StrCmp.  What is interesting about his “observation” is:

“Holy cow!  This means that if you use the equals operator... you're actually calling into old VB6 libraries using COM Interop.“

WHAT!?!?!  Where did he derive this conclusion from?  How about we look at the *ACTUAL* IL...

<FONT color=#1000a0>.method</FONT> <FONT color=#1000a0>public</FONT> <FONT color=#1000a0>static</FONT> <FONT color=#006018>int32</FONT> StrCmp(<FONT color=#006018>string</FONT> sLeft, <FONT color=#006018>string</FONT> sRight, <FONT color=#006018>bool</FONT> TextCompare)<FONT color=#1000a0> </FONT><FONT color=#1000a0>cil</FONT><FONT color=#1000a0> </FONT><FONT color=#1000a0>managed</FONT> {       <FONT color=#808080>// Code Size: 51 byte(s)</FONT>       <FONT color=#1000a0>.maxstack</FONT> 4       <FONT color=#1000a0>.locals</FONT> <FONT color=#1000a0>init</FONT> (             <FONT color=#006018>int32</FONT> num1)       L_0000: <FONT color=#006018>ldarg.0</FONT>        L_0001: <FONT color=#006018>brtrue.s</FONT> L_000a       L_0003: <FONT color=#006018>ldstr</FONT> <FONT color=#800000>""</FONT>       L_0008: <FONT color=#006018>starg.s</FONT> sLeft       L_000a: <FONT color=#006018>ldarg.1</FONT>        L_000b: <FONT color=#006018>brtrue.s</FONT> L_0014       L_000d: <FONT color=#006018>ldstr</FONT> <FONT color=#800000>""</FONT>       L_0012: <FONT color=#006018>starg.s</FONT> sRight       L_0014: <FONT color=#006018>ldarg.2</FONT>        L_0015: <FONT color=#006018>brfalse.s</FONT> L_002b       L_0017: <FONT color=#006018>call</FONT> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CultureInfo href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=7"><FONT color=#006018>System.Globalization.CultureInfo</FONT></A> <A title=Microsoft.VisualBasic.CompilerServices.Utils href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=8"><FONT color=#006018>Microsoft.VisualBasic.CompilerServices.Utils</FONT></A>::<FONT color=#006018>GetCultureInfo</FONT>()       L_001c: <FONT color=#006018>callvirt</FONT> <FONT color=#1000a0>instance</FONT> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CompareInfo href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=11"><FONT color=#006018>System.Globalization.CompareInfo</FONT></A> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CultureInfo href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=13"><FONT color=#006018>System.Globalization.CultureInfo</FONT></A>::<FONT color=#006018>get_CompareInfo</FONT>()       L_0021: <FONT color=#006018>ldarg.0</FONT>        L_0022: <FONT color=#006018>ldarg.1</FONT>        L_0023: <FONT color=#006018>ldc.i4.s</FONT> <FONT color=#800000>25</FONT>       L_0025: <FONT color=#006018>callvirt</FONT> <FONT color=#1000a0>instance</FONT> <FONT color=#006018>int32</FONT> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CompareInfo href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=17"><FONT color=#006018>System.Globalization.CompareInfo</FONT></A>::<FONT color=#006018>Compare</FONT>(<FONT color=#006018>string</FONT>, <FONT color=#006018>string</FONT>, [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CompareOptions href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=22"><FONT color=#006018>System.Globalization.CompareOptions</FONT></A>)       L_002a: <FONT color=#006018>ret</FONT>        L_002b: <FONT color=#006018>ldarg.0</FONT>        L_002c: <FONT color=#006018>ldarg.1</FONT>        L_002d: <FONT color=#006018>call</FONT> <FONT color=#006018>int32</FONT> <FONT color=#006018>string</FONT>::<FONT color=#006018>CompareOrdinal</FONT>(<FONT color=#006018>string</FONT>, <FONT color=#006018>string</FONT>)       L_0032: <FONT color=#006018>ret</FONT>  }

OK, I see some calls to another Visual Basic library method to gather the current culture information.  Let's see what the IL for the additional VB library method is...

<FONT color=#1000a0>.method</FONT> <FONT color=#1000a0>assembly</FONT> <FONT color=#1000a0>static</FONT> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CultureInfo href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=2"><FONT color=#006018>System.Globalization.CultureInfo</FONT></A> GetCultureInfo()<FONT color=#1000a0> </FONT><FONT color=#1000a0>cil</FONT><FONT color=#1000a0> </FONT><FONT color=#1000a0>managed</FONT> {       <FONT color=#808080>// Code Size: 11 byte(s)</FONT>       <FONT color=#1000a0>.maxstack</FONT> 1       <FONT color=#1000a0>.locals</FONT> <FONT color=#1000a0>init</FONT> (             [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CultureInfo href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=4"><FONT color=#006018>System.Globalization.CultureInfo</FONT></A> info1)       L_0000: <FONT color=#006018>call</FONT> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Threading.Thread href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=6"><FONT color=#006018>System.Threading.Thread</FONT></A> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Threading.Thread href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=8"><FONT color=#006018>System.Threading.Thread</FONT></A>::<FONT color=#006018>get_CurrentThread</FONT>()       L_0005: <FONT color=#006018>callvirt</FONT> <FONT color=#1000a0>instance</FONT> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Globalization.CultureInfo href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=11"><FONT color=#006018>System.Globalization.CultureInfo</FONT></A> [<FONT color=#006018>mscorlib</FONT>]<A title=System.Threading.Thread href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=13"><FONT color=#006018>System.Threading.Thread</FONT></A>::<FONT color=#006018>get_CurrentCulture</FONT>()       L_000a: <FONT color=#006018>ret</FONT>  }
The rest of the calls, meaning the non-VB library methods, in these methods are *ALL* in the .NET Framework.  There is absolutely *NO* call to any VB6 libraries in this call.  Again, where did he derive his conclusion from?  Oh, so maybe he just mis-spoke... let's continue on...

“When this occurs, you get a major lag because you have to marshal the string out of managed memory into unmanaged memory.  Using the .Equals method on the other hand keeps everything in managed memory and does a much faster comparison.  That explains the difference.” 

He is definitely talking about P/Invoke (whether it be COM or Win32) since states the string being marshalled between managed and unmanaged memory.  Hard to mistake what his conclusion was.  He then continues on to refer to at least one member of the VB team being an “absolute moron“.

“My question is... Who was the absolute moron on the VB.NET compiler team who made that decision?“

Um... based on what you've read so far, who's the moron... anyway, let's continue on...

He continues on to discuss how C# is soooooo superior in this regard.  However, he finds that in C# that the = operator and .Equals are different in speed as well.  By his numbers, a factor of 4.  So let's look at the performance...

He points out that C# utilizes the op_Equality method (what the == operator in C# gets translated to at the compiler level).  Here's it's IL.

<FONT color=#1000a0>.method</FONT> <FONT color=#1000a0>public</FONT> <FONT color=#1000a0>hidebysig</FONT> <FONT color=#1000a0>specialname</FONT> <FONT color=#1000a0>static</FONT> <FONT color=#006018>bool</FONT> op_Equality(<FONT color=#006018>string</FONT> a, <FONT color=#006018>string</FONT> b)<FONT color=#1000a0> </FONT><FONT color=#1000a0>cil</FONT><FONT color=#1000a0> </FONT><FONT color=#1000a0>managed</FONT> {       <FONT color=#808080>// Code Size: 8 byte(s)</FONT>       <FONT color=#1000a0>.maxstack</FONT> 8       L_0000: <FONT color=#006018>ldarg.0</FONT>        L_0001: <FONT color=#006018>ldarg.1</FONT>        L_0002: <FONT color=#006018>call</FONT> <FONT color=#006018>bool</FONT> <FONT color=#006018>string</FONT>::<FONT color=#006018>Equals</FONT>(<FONT color=#006018>string</FONT>, <FONT color=#006018>string</FONT>)       L_0007: <FONT color=#006018>ret</FONT>  }

As you can see, internally it calls upon the .Equals everyone seems to like to discuss.  However, how is it that the inclusion of just 4 opcodes could introduce a speed difference by a factor of 4?  It can't.  Essentially, the timings his code reflects can't be counted upon since he's only running a single iteration.  The test should be done drastically different since in this single iteration many other factors could be occurring such as memory management on the machine, other applications running (CPU load), JIT, etc.  You'd have to take a sampling of timings across several iterations to gather some numbers that mean anything significant.

So based on this single pass test, his numbers showed as VB's version being 200 times slower.  This is also not true.  It is true that it is slower than the C# counterpart (read further for updates in Visual Basic 2005).  However, the performance hit is nothing near a factor of 200.  My testing has shown this to be more of a factor of 6-7.  Yes, slower than C#, but not that slow.  What causes the difference?  In VB it's doing two additional checks.  Let's look at the IL again (this time, converted to VB for easier reading).

<FONT color=#1000a0>Public</FONT> <FONT color=#1000a0>Shared</FONT> <FONT color=#1000a0>Function</FONT> StrCmp(<FONT color=#1000a0>ByVal</FONT> sLeft<FONT color=#1000a0> As </FONT><A title=System.String href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=1"><FONT color=#006018>String</FONT></A>, <FONT color=#1000a0>ByVal</FONT> sRight<FONT color=#1000a0> As </FONT><A title=System.String href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=2"><FONT color=#006018>String</FONT></A>, <FONT color=#1000a0>ByVal</FONT> TextCompare<FONT color=#1000a0> As </FONT><A title=System.Boolean href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=3"><FONT color=#006018>Boolean</FONT></A>)<FONT color=#1000a0> As </FONT><A title=System.Int32 href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=4"><FONT color=#006018>Integer</FONT></A> <FONT color=#1000a0>  If</FONT> (<FONT color=#006018>sLeft</FONT> <FONT color=#1000a0>Is</FONT> <FONT color=#800000>Nothing</FONT>) <FONT color=#1000a0>Then</FONT>     <FONT color=#006018>sLeft</FONT> = <FONT color=#800000>""</FONT>   <FONT color=#1000a0>End If</FONT>   <FONT color=#1000a0>If</FONT> (<FONT color=#006018>sRight</FONT> <FONT color=#1000a0>Is</FONT> <FONT color=#800000>Nothing</FONT>) <FONT color=#1000a0>Then</FONT>     <FONT color=#006018>sRight</FONT> = <FONT color=#800000>""</FONT>   <FONT color=#1000a0>End If</FONT>   <FONT color=#1000a0>If</FONT> <FONT color=#006018>TextCompare</FONT> <FONT color=#1000a0>Then</FONT>     <FONT color=#1000a0>Return</FONT> <A title=Microsoft.VisualBasic.CompilerServices.Utils href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=5"><FONT color=#006018>Utils</FONT></A>.<A title=Microsoft.VisualBasic.CompilerServices.Utils.GetCultureInfo() href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=6"><FONT color=#006018>GetCultureInfo</FONT></A>.<FONT color=#006018>CompareInfo</FONT>.<FONT color=#006018>Compare</FONT>(<FONT color=#006018>sLeft</FONT>, <FONT color=#006018>sRight</FONT>, (<A title=System.Globalization.CompareOptions href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=9"><FONT color=#006018>CompareOptions</FONT></A>.<FONT color=#006018>IgnoreWidth</FONT> <FONT color=#1000a0>Or</FONT> (<A title=System.Globalization.CompareOptions href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=11"><FONT color=#006018>CompareOptions</FONT></A>.<FONT color=#006018>IgnoreKanaType</FONT> <FONT color=#1000a0>Or</FONT> <A title=System.Globalization.CompareOptions href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=13"><FONT color=#006018>CompareOptions</FONT></A>.<FONT color=#006018>IgnoreCase</FONT>)))   <FONT color=#1000a0>End If</FONT>   <FONT color=#1000a0>Return</FONT> <A title=System.String href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=15"><FONT color=#006018>String</FONT></A>.<FONT color=#006018>CompareOrdinal</FONT>(<FONT color=#006018>sLeft</FONT>, <FONT color=#006018>sRight</FONT>) <FONT color=#1000a0>End Function</FONT>

The VB StrCmp method will automatically handle Nothing (or NULL) strings in the comparison.  This could be good or bad depending on your point of view since “” could equal Nothing.  However, most of the time this behavior is preferred since you don't have to specifically worry about the difference between an empty string and a Nothing string.  If you need to know, then you could check for it specifically.

This is where the slight performance hit comes in... again it's not hit by a factor of 200, but rather more like 6-7... which is 25% to 33% over the C# counterpart.  In addition, the VB StrCmp ends up calling upon String.CompareOrdinal, which in my testing has shown to be slightly faster than String.Equals... so if you want to get picky... C# isn't as fast as it could be.

Up to this point, I've been discussing VB7.1 (aka the Visual Basic .NET 2003 / .NET 1.1).   What about VB8 (Visual Basic 2005 / .NET 2.0)?  In VB8, the StrCmp method has been updated...

<FONT color=#1000a0>Public</FONT> <FONT color=#1000a0>Shared</FONT> <FONT color=#1000a0>Function</FONT> StrCmp(<FONT color=#1000a0>ByVal</FONT> sLeft<FONT color=#1000a0> As </FONT><A title=System.String href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=1"><FONT color=#006018>String</FONT></A>, <FONT color=#1000a0>ByVal</FONT> sRight<FONT color=#1000a0> As </FONT><A title=System.String href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=2"><FONT color=#006018>String</FONT></A>, <FONT color=#1000a0>ByVal</FONT> TextCompare<FONT color=#1000a0> As </FONT><A title=System.Boolean href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=3"><FONT color=#006018>Boolean</FONT></A>)<FONT color=#1000a0> As </FONT><A title=System.Int32 href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=4"><FONT color=#006018>Integer</FONT></A> <FONT color=#1000a0>  If</FONT> (<FONT color=#006018>sLeft</FONT> <FONT color=#1000a0>Is</FONT> <FONT color=#006018>sRight</FONT>) <FONT color=#1000a0>Then</FONT>     <FONT color=#1000a0>Return</FONT> <FONT color=#800000>0</FONT>   <FONT color=#1000a0>End If</FONT>   <FONT color=#1000a0>If</FONT> (<FONT color=#006018>sLeft</FONT> <FONT color=#1000a0>Is</FONT> <FONT color=#800000>Nothing</FONT>) <FONT color=#1000a0>Then</FONT>     <FONT color=#1000a0>If</FONT> (<FONT color=#006018>sRight</FONT>.<FONT color=#006018>Length</FONT> = <FONT color=#800000>0</FONT>) <FONT color=#1000a0>Then</FONT>       <FONT color=#1000a0>Return</FONT> <FONT color=#800000>0</FONT>     <FONT color=#1000a0>End If</FONT>     <FONT color=#1000a0>Return</FONT> <FONT color=#800000>-1</FONT>   <FONT color=#1000a0>End If</FONT>   <FONT color=#1000a0>If</FONT> (<FONT color=#006018>sRight</FONT> <FONT color=#1000a0>Is</FONT> <FONT color=#800000>Nothing</FONT>) <FONT color=#1000a0>Then</FONT>     <FONT color=#1000a0>If</FONT> (<FONT color=#006018>sLeft</FONT>.<FONT color=#006018>Length</FONT> = <FONT color=#800000>0</FONT>) <FONT color=#1000a0>Then</FONT>       <FONT color=#1000a0>Return</FONT> <FONT color=#800000>0</FONT>     <FONT color=#1000a0>End If</FONT>     <FONT color=#1000a0>Return</FONT> <FONT color=#800000>1</FONT>   <FONT color=#1000a0>End If</FONT>   <FONT color=#1000a0>If</FONT> <FONT color=#006018>TextCompare</FONT> <FONT color=#1000a0>Then</FONT>     <FONT color=#1000a0>Return</FONT> <A title=Microsoft.VisualBasic.CompilerServices.Utils href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=7"><FONT color=#006018>Utils</FONT></A>.<A title=Microsoft.VisualBasic.CompilerServices.Utils.GetCultureInfo() href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=8"><FONT color=#006018>GetCultureInfo</FONT></A>.<FONT color=#006018>CompareInfo</FONT>.<FONT color=#006018>Compare</FONT>(<FONT color=#006018>sLeft</FONT>, <FONT color=#006018>sRight</FONT>, (<A title=System.Globalization.CompareOptions href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=11"><FONT color=#006018>CompareOptions</FONT></A>.<FONT color=#006018>IgnoreWidth</FONT> <FONT color=#1000a0>Or</FONT> (<A title=System.Globalization.CompareOptions href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=13"><FONT color=#006018>CompareOptions</FONT></A>.<FONT color=#006018>IgnoreKanaType</FONT> <FONT color=#1000a0>Or</FONT> <A title=System.Globalization.CompareOptions href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=15"><FONT color=#006018>CompareOptions</FONT></A>.<FONT color=#006018>IgnoreCase</FONT>)))   <FONT color=#1000a0>End If</FONT>   <FONT color=#1000a0>Return</FONT> <A title=System.String href="http://www.aisto.com/roeder/dotnet/Default.aspx?Object=17"><FONT color=#006018>String</FONT></A>.<FONT color=#006018>CompareOrdinal</FONT>(<FONT color=#006018>sLeft</FONT>, <FONT color=#006018>sRight</FONT>) <FONT color=#1000a0>End Function</FONT>

A few changes as you can see, so how is the performance?   Well, probably not due specifically to the changes made in the method that the = operator has gone through, although in some cases, it is possible that the new method could be a lot faster since it may never even make it to the CompareOrdinal call under the right conditions.  .NET 2.0 code just seems to be a bit faster overall and this is probably where some of the speed improvement has come from.  So what is the improvement?  Let me let someone else point out a difference (even though they decided to post anonymously... go figure)...

Equals Operator: 3.7993655618242E-05 sec
Equals Function: 3.43619091253218E-05 sec

Hmmm... not much of a difference there.  But instead of taking this guys word for it, let's look at my own testing... one where I do several iterations [1].  After 100,000 iterations, doing a raw addition of the individual timings and comparing these across three different operations (= operator, String.Equals, and String.CompareOrdinal), the numbers are rather interesting...

VB version:

= Operator:                     0.177467527297846 : 102.65 %
String.CompareOrdinal:   0.177394892368044 : 102.61 %
String.Equals:                   0.172890688621453 : 100.00 %

C# version:

= Operator:                     0.166826230708464 : 104.47 %
String.CompareOrdinal:   0.173935234785814 : 108.92 %
String.Equals:                  0.159690407580096 : 100.00 %

Essentially, I did a percentage comparison using String.Equals as the baseline.  What's interesting is that sometimes when running this test, I have seen drastically different timings when running in “debug” mode.  Here's the C# timings (for the naysayers out there)...

= Operator:                     2.04699200596579 : 107.95 %
String.CompareOrdinal:   1.8898748304617 : 99.67 %
String.Equals:                   1.89616278046669 : 100.00 %

Not only is the timings drastically different, notice that the String.CompareOrdinal is actually faster than String.Equals.  This has been pretty much consistent in my testing.  I have however seen String.CompareOrdinal run faster than String.Equals in release builds as well, so who knows what is really happening with such a simple test.  And if you run enough tests, you'll see results like this...

VB version:

= Operator:                    0.186439896691224 : 94.34 %
String.CompareOrdinal:  0.16090648392503 : 81.42 %
String.Equals:                 0.197625955254675 : 100.00 %

So in at least one test, the VB version was actually faster than String.Equals. 

What does all this mean?  It means a few things.

  • Don't listen to people like this.  No matter how much crap they try to spout, check their facts if their claims seems to be amazingly silly.
  • Don't rely on other peoples “testing“ to base your performance decisions upon.  In the end, you have to take into account your specific needs (or more specifically, your codes needs) and test it accordingly.  You might have a need to try to gain a few microseconds of performance by utilizing String.Equals or String.CompareOrdinal directly.  However, I believe that you would probably be gaining that overhead right back by having to do the extra tests that would have been handled for you already if you'd just use the operator.  But that's me... like I said, your individual circumstance, it might be a performance improvement.
  • Performance is more than just “raw numbers“.  Circumstances play a huge part of what makes up your overall application performance.  Saving a few opcodes here and there when that part of the code is only running once every so often isn't going to help you improve your applications overall performance.  Performance improvement is more a case-by-case discussion rather than a blanket argument.  Sure, there are some best practices, but not utilizing the = operator is *NOT* one of them.
  • **VB IS NOT INFERIOR**... in many respects *IT IS SUPERIOR*.  Yes, I said it.  There.  Deal with it.  Think I'm wrong, by all means, let me know.  (Leave out the syntax argument... if you like semicolons and curly braces... fine... that's a preference, not an argument as to why one is better than the other... bring me “real“ issues.)  ;-) 

[1] - Download code (Yes, there's a VB and C# version of the code in the zip file.).

This post is licensed under CC BY 4.0 by the author.