Post

(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…


.method public static int32 StrCmp(string sLeft, string sRight, bool TextCompare) cil managed
{
  // Code Size: 51 byte(s)
  .maxstack 4
  .locals init (
        int32 num1)
  L_0000: ldarg.0 
  L_0001: brtrue.s L_000a
  L_0003: ldstr ""
  L_0008: starg.s sLeft
  L_000a: ldarg.1 
  L_000b: brtrue.s L_0014
  L_000d: ldstr ""
  L_0012: starg.s sRight
  L_0014: ldarg.2 
  L_0015: brfalse.s L_002b
  L_0017: call [mscorlib]System.Globalization.CultureInfo Microsoft.VisualBasic.CompilerServices.Utils::GetCultureInfo()
  L_001c: callvirt instance [mscorlib]System.Globalization.CompareInfo [mscorlib]System.Globalization.CultureInfo::get_CompareInfo()
  L_0021: ldarg.0 
  L_0022: ldarg.1 
  L_0023: ldc.i4.s 25
  L_0025: callvirt instance int32 [mscorlib]System.Globalization.CompareInfo::Compare(string, string, [mscorlib]System.Globalization.CompareOptions)
  L_002a: ret 
  L_002b: ldarg.0 
  L_002c: ldarg.1 
  L_002d: call int32 string::CompareOrdinal(string, string)
  L_0032: ret 
}

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…

.method assembly static [mscorlib]System.Globalization.CultureInfo GetCultureInfo() cil managed
{
  // Code Size: 11 byte(s)
  .maxstack 1
  .locals init (
        [mscorlib]System.Globalization.CultureInfo info1)
  L_0000: call [mscorlib]System.Threading.Thread [mscorlib]System.Threading.Thread::get_CurrentThread()
  L_0005: callvirt instance [mscorlib]System.Globalization.CultureInfo [mscorlib]System.Threading.Thread::get_CurrentCulture()
  L_000a: ret 
}

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.

.method public hidebysig specialname static bool op_Equality(string a, string b) cil managed
{
  // Code Size: 8 byte(s)
  .maxstack 8
  L_0000: ldarg.0 
  L_0001: ldarg.1 
  L_0002: call bool string::Equals(string, string)
  L_0007: ret 
}

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).

1
2
3
4
5
6
7
8
9
10
11
12
Public Shared Function StrCmp(ByVal sLeft As String, ByVal sRight As String, ByVal TextCompare As Boolean) As Integer
  If (sLeft Is Nothing) Then
    sLeft = ""
  End If
  If (sRight Is Nothing) Then
    sRight = ""
  End If
  If TextCompare Then
    Return Utils.GetCultureInfo.CompareInfo.Compare(sLeft, sRight, (CompareOptions.IgnoreWidth Or (CompareOptions.IgnoreKanaType Or CompareOptions.IgnoreCase)))
  End If
  Return String.CompareOrdinal(sLeft, sRight)
End Function

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…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Public Shared Function StrCmp(ByVal sLeft As String, ByVal sRight As String, ByVal TextCompare As Boolean) As Integer
  If (sLeft Is sRight) Then
    Return 0
  End If
  If (sLeft Is Nothing) Then
    If (sRight.Length = 0) Then
      Return 0
    End If
    Return -1
  End If
  If (sRight Is Nothing) Then
    If (sLeft.Length = 0) Then
      Return 0
    End If
    Return 1
  End If
  If TextCompare Then
    Return Utils.GetCultureInfo.CompareInfo.Compare(sLeft, sRight, (CompareOptions.IgnoreWidth Or (CompareOptions.IgnoreKanaType Or CompareOptions.IgnoreCase)))
  End If
  Return String.CompareOrdinal(sLeft, sRight)
End Function

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.