VB.NET 中的 foreach 比 C# 中的 foreach 快吗?

发布于 2024-07-15 02:38:45 字数 228 浏览 6 评论 0原文

我的同事说,在之前的一次采访中,他了解到VB.Net中的foreach比c#的foreach更快。 他被告知这是因为两者都有不同的 CLR 实现。

从 C++ 的角度来看,我很好奇这是为什么,有人告诉我我需要先阅读 CLR。 谷歌搜索 foreach 和 CLR 并不能帮助我理解。

有人能很好地解释为什么 foreach 在 VB.Net 中比在 C# 中更快吗? 还是我的同事被误导了?

My co-worker said that in a previous interview, he learned that foreach is faster in VB.Net than c#'s foreach. He was told that this was because both have different CLR implementation.

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. Googling foreach and CLR doesn't help me understand.

Does anyone have a good explanation on why foreach is faster in VB.Net than in c#? Or was my co-worker misinformed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

苯莒 2024-07-22 02:38:45

C# 和 VB.Net 在 IL 级别上没有显着差异。 两个版本之间到处都抛出了一些额外的 Nop 指令,但没有任何实际改变正在发生的事情。

方法如下:(在 C# 中)

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

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

在 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

这是 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
}

这是 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
}

There is no significant difference at the IL level between C# and VB.Net. There are some additional Nop instructions thrown in here and there between the two versions, but nothing that actually changes what is going on.

Here is the method: (in 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:

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:

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

.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
}
红焚 2024-07-22 02:38:45

我对这个说法有点怀疑。 foreach 构造对两种语言的工作方式相同,因为它获取 IEnumerator 来自托管对象并对其调用 MoveNext()。 原始代码是用 VB.NET 还是 C# 编写的并不重要,它们都编译成相同的东西。

在我的测试计时中,对于很长的迭代,VB.NET 和 C# 中的相同 foreach 循环的间隔永远不会超过 ~1%。

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:

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

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. Whether the original code was written in VB.NET or c# should not matter, they both compile to the same thing.

In my test timings, the same foreach loop in VB.NET and c# were never more than ~1% apart for very long iterations.

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:

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
像你 2024-07-22 02:38:45

对于循环字符串数组的简单 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

这是 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

唯一的区别是 VB 使用 add.ovfconv .ovf.i4 而不是 addconv.i4。 这意味着 VB 代码会执行两次额外的溢出检查,并且可能会稍微慢一些。

For a simple foreach looping a string array, this is the IL code produced by VB:

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#:

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. That means that the VB code does two extra overflow checks, and might be slightly slower.

始终不够爱げ你 2024-07-22 02:38:45

VB.NET 和 C# 都使用相同的 CLR。 我只是使用以下代码做了一个快速的基准测试:

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 版本:

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

在执行 500000 次迭代的发布版本(最重要)上,C# 代码稍微快一点,但也仅差一点点。

调试版本:

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

发布版本:

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

VB.NET and C# both use the same CLR. I just did a quick finger in the air benchmark using the following code:

C# version:

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:

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.

Debug Build:

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

Release Build:

C#     - 0m 56s 179ms
VB.NET - 0m 56s 327ms
帥小哥 2024-07-22 02:38:45

你应该做一个实验。 抓住(很棒的).NET Reflector,用每种语言构建一个简单的测试用例,看看生成的MSIL是否相同。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文