Something interesting I came across today. Two seemingly identical loops in two different languages:
for(int i = 0; i <= 10000000; i++)
{
TimeSpan t2 = DateTime.Now.Subtract(t);
// assume we do something more interesting in
// a real program...
}
Dim t As DateTime = DateTime.Now()
For i As Integer = 0 To 10000000
Dim t2 As TimeSpan = Now.Subtract(t)
' assume we do something more interesting in a
' real program...
t2 = Nothing
Next
There is a difference, obviously—the VB programmer has attempted to set the TimeSpan t2 (a ValueType) to Nothing. The VB compiler doesn’t prohibit you from doing this, as the C# compiler would if that programmer had tried to set t2 = null.
Programmers at my company (other than me) do this constantly. The reason is that they got used to doing it to every object variable in VB6. Whether or not that was actually necessary, I don’t know. It’s beyond the scope of this post, anyway.
At any rate, it shouldn’t matter. At most, it should end up just being one instruction and might even get tossed out by the JIT compiler. Right? Wrong.
On average, the C# loop executes more than 5 times faster. Roughly 5 seconds for VB versus roughly one second for C# on my machine. When you remove the extra line in VB, the times are comparable. If we replace TimeSpan with a reference type but leave the extra line, the times are comparable.
Why? Looking at the MSIL generated when we have this gives us this in Release mode:
; Equivalent to GetType(TimeSpan)
L_001d: ldtoken [mscorlib]System.TimeSpan
L_0022: call [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle([mscorlib]System.RuntimeTypeHandle)
; This is the late-binding way of creating an empty TimeSpan.
L_0027: call object [mscorlib]System.Activator::CreateInstance([mscorlib]System.Type)
L_002c: unbox [mscorlib]System.TimeSpan
L_0031: ldobj [mscorlib]System.TimeSpan
L_0036: stloc.2
Ok, so I can see now what the compiler is trying to do. Whereas the C# compiler would make us explicitly go looking for the TimeSpan.Zero field if we wanted to reset the value of a variable, the VB compiler is trying to help us out by creating an empty structure for us.
It would have saved us a little trouble, if that was at all what we were intending to do. In my case, it just happened because someone is used to typing it. Another problem is that it happens where, even understanding this behavior, you would still not expect it. Consider:
' Reminder - DictionaryEntry is a struct.
For Each entry As DictionaryEntry In hash
' ...
Next
This ends up being equivalent to setting entry = Nothing with each iteration. You’d therefore get the same late binding IL and this loop is many times slower than the “same” loop in C#.
I’m not a language or compiler designer, but here’s what I would like to see done (in order of preference).
- Disallow this with Option Strict On, like C# does currently (a breaking change, obviously).
- Give a compiler warning where it will happen.
- Do a better job of publicizing this kind of behavior. I can’t find any documentation on it.