简体   繁体   English

VB.NET中的foreach比c#更快吗?

[英]Is the foreach in VB.NET faster than in c#?

My co-worker said that in a previous interview, he learned that foreach is faster in VB.Net than c#'s foreach. 我的同事说,在之前的一次采访中,他了解到VB中的foreach比c#的foreach更快。 He was told that this was because both have different CLR implementation. 他被告知这是因为两者都有不同的CLR实现。

Coming from a C++ perspective, I'm curious on why this is and I was told that I need to read up on CLR first. 从C ++的角度来看,我很好奇为什么会这样,而且我被告知我需要先阅读CLR。 Googling foreach and CLR doesn't help me understand. 谷歌搜索foreach和CLR并不能帮助我理解。

Does anyone have a good explanation on why foreach is faster in VB.Net than in c#? 有没有人能够很好地解释为什么foreach在VB.Net中比在c#中更快? Or was my co-worker misinformed? 还是我的同事误导了?

There is no significant difference at the IL level between C# and VB.Net. C#和VB.Net之间的IL级别没有显着差异。 There are some additional Nop instructions thrown in here and there between the two versions, but nothing that actually changes what is going on. 在这两个版本之间有一些额外的Nop指令,但实际上并没有改变发生的事情。

Here is the method: (in C#) 这是方法:(在C#中)

public void TestForEach()
    {
        List<string> items = new List<string> { "one", "two", "three" };

        foreach (string item in items)
        {
            Debug.WriteLine(item);
        }
    }

And in VB.Net: 在VB.Net中:

Public Sub TestForEach
    Dim items As List(Of String) = New List(Of String)()
    items.Add("one")
    items.Add("two")
    items.Add("three")
    For Each item As string In items
        Debug.WriteLine(item)
    Next
End Sub

Here is the IL for the C# version: 这是C#版本的IL:

.method public hidebysig instance void TestForEach() cil managed
{
    .maxstack 2
    .locals init (
        [0] class [mscorlib]System.Collections.Generic.List`1<string> items,
        [1] string item,
        [2] class [mscorlib]System.Collections.Generic.List`1<string> <>g__initLocal3,
        [3] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string> CS$5$0000,
        [4] bool CS$4$0001)
    L_0000: nop 
    L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
    L_0006: stloc.2 
    L_0007: ldloc.2 
    L_0008: ldstr "one"
    L_000d: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_0012: nop 
    L_0013: ldloc.2 
    L_0014: ldstr "two"
    L_0019: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_001e: nop 
    L_001f: ldloc.2 
    L_0020: ldstr "three"
    L_0025: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_002a: nop 
    L_002b: ldloc.2 
    L_002c: stloc.0 
    L_002d: nop 
    L_002e: ldloc.0 
    L_002f: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
    L_0034: stloc.3 
    L_0035: br.s L_0048
    L_0037: ldloca.s CS$5$0000
    L_0039: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
    L_003e: stloc.1 
    L_003f: nop 
    L_0040: ldloc.1 
    L_0041: call void [System]System.Diagnostics.Debug::WriteLine(string)
    L_0046: nop 
    L_0047: nop 
    L_0048: ldloca.s CS$5$0000
    L_004a: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
    L_004f: stloc.s CS$4$0001
    L_0051: ldloc.s CS$4$0001
    L_0053: brtrue.s L_0037
    L_0055: leave.s L_0066
    L_0057: ldloca.s CS$5$0000
    L_0059: constrained [mscorlib]System.Collections.Generic.List`1/Enumerator<string>
    L_005f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0064: nop 
    L_0065: endfinally 
    L_0066: nop 
    L_0067: ret 
    .try L_0035 to L_0057 finally handler L_0057 to L_0066
}

Here is the IL for the VB.Net version: 这是VB.Net版本的IL:

.method public instance void TestForEach() cil managed
{
    .maxstack 2
    .locals init (
        [0] class [mscorlib]System.Collections.Generic.List`1<string> items,
        [1] string item,
        [2] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string> VB$t_struct$L0,
        [3] bool VB$CG$t_bool$S0)
    L_0000: nop 
    L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldstr "one"
    L_000d: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_0012: nop 
    L_0013: ldloc.0 
    L_0014: ldstr "two"
    L_0019: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_001e: nop 
    L_001f: ldloc.0 
    L_0020: ldstr "three"
    L_0025: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_002a: nop 
    L_002b: nop 
    L_002c: ldloc.0 
    L_002d: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
    L_0032: stloc.2 
    L_0033: br.s L_0045
    L_0035: ldloca.s VB$t_struct$L0
    L_0037: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
    L_003c: stloc.1 
    L_003d: ldloc.1 
    L_003e: call void [System]System.Diagnostics.Debug::WriteLine(string)
    L_0043: nop 
    L_0044: nop 
    L_0045: ldloca.s VB$t_struct$L0
    L_0047: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
    L_004c: stloc.3 
    L_004d: ldloc.3 
    L_004e: brtrue.s L_0035
    L_0050: nop 
    L_0051: leave.s L_0062
    L_0053: ldloca.s VB$t_struct$L0
    L_0055: constrained [mscorlib]System.Collections.Generic.List`1/Enumerator<string>
    L_005b: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0060: nop 
    L_0061: endfinally 
    L_0062: nop 
    L_0063: ret 
    .try L_002c to L_0053 finally handler L_0053 to L_0062
}

I'm a little suspicious of this claim. 我对这种说法有点怀疑。 The foreach construct works the same way against both languages, in that it gets the IEnumerator from the managed object and calls MoveNext() on it. foreach构造对两种语言的工作方式相同,因为它从托管对象获取IEnumerator并在其上调用MoveNext()。 Whether the original code was written in VB.NET or c# should not matter, they both compile to the same thing. 无论原始代码是用VB.NET还是用c#编写都没关系,它们都编译成同样的东西。

In my test timings, the same foreach loop in VB.NET and c# were never more than ~1% apart for very long iterations. 在我的测试时间中,VB.NET和c#中的相同foreach循环在很长的迭代中间隔不超过1%。

c#: C#:

L_0048: ldloca.s CS$5$0001
L_004a: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
L_004f: stloc.3 
L_0050: nop 
L_0051: ldloc.3 
L_0052: call void [mscorlib]System.Console::WriteLine(string)
L_0057: nop 
L_0058: nop 
L_0059: ldloca.s CS$5$0001
L_005b: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
L_0060: stloc.s CS$4$0000
L_0062: ldloc.s CS$4$0000
L_0064: brtrue.s L_0048

VB.NET: VB.NET:

L_0043: ldloca.s VB$t_struct$L0
L_0045: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
L_004a: stloc.s item
L_004c: ldloc.s item
L_004e: call void [mscorlib]System.Console::WriteLine(string)
L_0053: nop 
L_0054: nop 
L_0055: ldloca.s VB$t_struct$L0
L_0057: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
L_005c: stloc.s VB$CG$t_bool$S0
L_005e: ldloc.s VB$CG$t_bool$S0
L_0060: brtrue.s L_0043

For a simple foreach looping a string array, this is the IL code produced by VB: 对于循环字符串数组的简单foreach,这是由VB生成的IL代码:

L_0007: ldloc.0 
L_0008: stloc.3 
L_0009: ldc.i4.0 
L_000a: stloc.2 
L_000b: br.s L_0019

L_000d: ldloc.3 
L_000e: ldloc.2 
L_000f: ldelem.ref 
L_0010: stloc.1 

...

L_0015: ldloc.2 
L_0016: ldc.i4.1 
L_0017: add.ovf 
L_0018: stloc.2 

L_0019: ldloc.2 
L_001a: ldloc.3 
L_001b: ldlen 
L_001c: conv.ovf.i4 
L_001d: blt.s L_000d

And this is the IL code produced by C#: 这是C#生成的IL代码:

L_0007: ldloc.0 
L_0008: stloc.2 
L_0009: ldc.i4.0 
L_000a: stloc.3 
L_000b: br.s L_0019

L_000d: ldloc.2 
L_000e: ldloc.3 
L_000f: ldelem.ref 
L_0010: stloc.1 

...

L_0015: ldloc.3 
L_0016: ldc.i4.1 
L_0017: add 
L_0018: stloc.3 

L_0019: ldloc.3 
L_001a: ldloc.2 
L_001b: ldlen 
L_001c: conv.i4 
L_001d: blt.s L_000d

The only difference is that VB uses add.ovf and conv.ovf.i4 instead of add and conv.i4 . 唯一的区别是VB使用add.ovfconv.ovf.i4而不是addconv.i4 That means that the VB code does two extra overflow checks, and might be slightly slower. 这意味着VB代码执行了两次额外的溢出检查,并且可能稍慢。

VB.NET and C# both use the same CLR. VB.NET和C#都使用相同的CLR。 I just did a quick finger in the air benchmark using the following code: 我只是使用以下代码在空中基准测试中快速做了一下:

C# version: C#版本:

static void Main(string[] args)
{
    List<string> myList = new List<string>();

    for(int i = 0; i < 500000; i++)
    {
        myList.Add(i.ToString());
    }

    DateTime st = DateTime.Now;
    foreach(string s in myList)
    {
        Console.WriteLine(s);
    }
    DateTime et = DateTime.Now;

    Console.WriteLine(et - st);
    Console.ReadLine();
}

VB.NET version: VB.NET版本:

Module Module1

    Sub Main()
        Dim myList As List(Of String) = New List(Of String)

        For i = 1 To 500000
            myList.Add(i)
        Next

        Dim st, et
        st = DateTime.Now
        For Each s As String In myList
            Console.WriteLine(s)
        Next
        et = DateTime.Now

        Console.WriteLine(et - st)
        Console.ReadLine()
    End Sub

End Module

On the release build (which counts most) performing 500000 iterations the C# code is marginally faster but only by a whisker. 在发布版本(最重要的)执行500000次迭代时,C#代码稍微快一点,但只有一个胡须。

Debug Build: 调试版本:

C#     - 1m 40s 457ms
VB.NET - 1m 42s 022ms

Release Build: 发布版本:

C#     - 0m 56s 179ms
VB.NET - 0m 56s 327ms

You should do an experiment. 你应该做一个实验。 Grab the (awesome) .NET Reflector , build a simple test case in each language, and see whether the generated MSIL is the same or not. 抓住(真棒) .NET Reflector ,在每种语言中构建一个简单的测试用例,并查看生成的MSIL是否相同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM