通过接口枚举 - 性能损失

und*_*ned 14 c# performance ienumerable list

我与我的同事有一点争议(这非常接近圣战:))关于通过枚举VS通过枚举访问列表的性能.为了操作一些事实,我写了以下测试:

   static void Main(string[] args)
    {
        const int count = 10000000;

        var stopwatch = new Stopwatch();

        var list = new List<int>(count);

        var rnd = new Random();

        for (int i = 0; i < count; i++)
        {
            list.Add( rnd.Next());
        }

        const int repeat = 20;

        double indeces = 0;
        double forEach = 0;

        for (int iteration = 0; iteration < repeat; iteration++)
        {
            stopwatch.Restart();
            long tmp = 0;
            for (int i = 0; i < count; i++)
            {                    
                tmp += list[i];
            }

            indeces += stopwatch.Elapsed.TotalSeconds;
            stopwatch.Restart();
            foreach (var integer in list)
            {            
                tmp += integer;
            }

            forEach += stopwatch.Elapsed.TotalSeconds;
        }

        Console.WriteLine(indeces /repeat);
        Console.WriteLine(forEach /repeat);

    }
Run Code Online (Sandbox Code Playgroud)

实际上,它只是访问元素.

正如我所料,索引访问速度更快.这是我的机器上发布版本的结果:

    0.0347//index access
    0.0737//enumerating
Run Code Online (Sandbox Code Playgroud)

但是,我决定改变测试一点:

        //the same as before
        ...
        IEnumerable<int> listAsEnumerable = list;
        //the same as before
        ...
        foreach (var integer in listAsEnumerable)
        {                
            tmp += integer;
        }
        ...
Run Code Online (Sandbox Code Playgroud)

现在输出如下:

    0.0321//index access
    0.1246//enumerating (2x slower!)
Run Code Online (Sandbox Code Playgroud)

如果我们通过接口枚举相同的列表,性能会 慢2倍!

为什么会这样?

意味着"通过接口枚举比枚举实际列表慢2倍".

我的猜测是运行时使用不同的Enumerators:第一次测试中的列表和第二次测试中的一般列表.

Mar*_*ell 16

使用时List<T>,foreach实际上并没有使用IEnumerable<T>界面; 相反,它使用List<T>.Enumerator,这是一个struct.在平凡的层面上,这意味着稍微减少间接 - 不必去引用,使用静态调用而不是虚拟调用 - 以及更直接的实现.

这些差异非常小,在任何明智的现实生活中,差异都是噪音.然而,如果测试可能略微显foreach表现.

为了扩展这个:foreach实际上并不需要IEnumerable[<T>]- 它可以纯粹GetEnumerator()/ .MoveNext()/ .Current/ .Dispose()模式上工作; 这在2.0中的泛型之前尤为重要.

但是,只有在键入变量List<T>(具有自定义GetEnumerator()方法)时才可以执行此操作.一旦你有IEnumerable<T>,它必须使用IEnumerator<T>