C#.First()vs [0]

Arm*_*ots 7 c# linq collections performance

有兴趣,方法有什么不同.
所以,我创建了两个片段.

Snippet A 
List<int> a = new List<int>();
a.Add(4);
a.Add(6);
int b = a.First(); 
Run Code Online (Sandbox Code Playgroud)

Snippet B 
List<int> a = new List<int>();
a.Add(4);
a.Add(6);
int b = a[0]; 
Run Code Online (Sandbox Code Playgroud)

在IL,我们相信,所以

Snippet A IL
IL_0000:  nop         
IL_0001:  newobj      System.Collections.Generic.List<System.Int32>..ctor
IL_0006:  stloc.0     // a
IL_0007:  ldloc.0     // a
IL_0008:  ldc.i4.4    
IL_0009:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_000E:  nop         
IL_000F:  ldloc.0     // a
IL_0010:  ldc.i4.6    
IL_0011:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_0016:  nop         
IL_0017:  ldloc.0     // a
IL_0018:  call        System.Linq.Enumerable.First
IL_001D:  stloc.1     // b
IL_001E:  ret        
Run Code Online (Sandbox Code Playgroud)

Snippet B IL
IL_0000:  nop         
IL_0001:  newobj      System.Collections.Generic.List<System.Int32>..ctor
IL_0006:  stloc.0     // a
IL_0007:  ldloc.0     // a
IL_0008:  ldc.i4.4    
IL_0009:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_000E:  nop         
IL_000F:  ldloc.0     // a
IL_0010:  ldc.i4.6    
IL_0011:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_0016:  nop         
IL_0017:  ldloc.0     // a
IL_0018:  ldc.i4.0    
IL_0019:  callvirt    System.Collections.Generic.List<System.Int32>.get_Item
IL_001E:  stloc.1     // b
IL_001F:  ret  
Run Code Online (Sandbox Code Playgroud)

Snippet B产生了一个更多的IL命令,但最终接近哪个更快?

Fab*_*jan 7

你可以自己检查一下:

    static void Main()
    {
        List<long> resultsFirst = new List<long>();
        List<long> resultsIndex = new List<long>();

        Stopwatch s = new Stopwatch();

        for (int z = 0; z < 100; z++)
        {
            List<int>[] lists = new List<int>[10000];

            int temp = 0;

            for (int i = 0; i < lists.Length; i++)
                lists[i] = new List<int>() { 4, 6 };                

            s.Restart();

            for (int i = 0; i < lists.Length; i++)
                temp = lists[i].First();

            s.Stop();

            resultsFirst.Add(s.ElapsedTicks);

            s.Restart();

            for (int i = 0; i < lists.Length; i++)
                temp = lists[i][0];

            s.Stop();

            resultsIndex.Add(s.ElapsedTicks);
        }

        Console.WriteLine("LINQ First()  :   " + resultsFirst.Average());
        Console.WriteLine(Environment.NewLine);
        Console.WriteLine("By index      :   " + resultsIndex.Average());

        Console.ReadKey();
    }
Run Code Online (Sandbox Code Playgroud)

发布模式下的输出:

LINQ First():367

按指数:84

调试模式下的输出:

LINQ First():401

按索引:177

PS

方法First的源代码是:

public static TSource First<TSource>(this IEnumerable<TSource> source)
{
    IList<TSource> list = source as IList<TSource>;
    if (list != null)
    {
        if (list.Count > 0)
        {
            return list[0];
        }
    }
    else
    {
        using (IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                return enumerator.Current;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

转换操作source as IList<TSource>或创建Enumerator对象很可能是因为First()它相当慢的原因.

  • 不首先调用泛型方法`First <T>`会在运行时导致代码生成吗? (2认同)

Jak*_*rtz 5

Enumerable.First方法定义为

public static TSource First<TSource>(this IEnumerable<TSource> source) 
{
    if (source == null) throw Error.ArgumentNull("source");
    IList<TSource> list = source as IList<TSource>;
    if (list != null) {
        if (list.Count > 0) return list[0];
    }
    else {
        using (IEnumerator<TSource> e = source.GetEnumerator()) {
            if (e.MoveNext()) return e.Current;
        }
    }
    throw Error.NoElements();
}
Run Code Online (Sandbox Code Playgroud)

因此,对于 aList<T>它最终在空检查和强制转换后使用索引器。看起来不多,但是当我测试性能时,First它比索引器慢 10 倍(for循环,10 000 000 次迭代,发布版本:第一个 - 100 毫秒,索引器 - 10 毫秒)。

  • @astef你能举一个我的答案不正确的现有框架版本的例子吗?我也无法想象未来框架的任何变化都会使结果有很大不同。`Enumerable.First` 已经针对 `IList&lt;T&gt;` 进行了优化。额外的步骤是必要的,我不认为它们可以通过 JIT 进行优化。唯一能改变结果的就是更快的施法。 (2认同)
  • @astef 在.NET 3.5之前没有`Enumerable.First`,那么我们如何谈论它的性能呢?我并不是假设实施过程中不会发生任何变化。我假设无论做出什么改变,都不会显着改变结果。IMO 可以安全地假设,当我们有一个专门的操作和一个通用的操作时,专门的操作会更快(好吧,不是更慢 - 通用的操作可以编译/编译为专门的操作) (2认同)