Iterating T []的开销转换为IList <T>

Gen*_*ade 13 c# generics collections performance

我注意到迭代了一个原始集合(T [])的性能损失,该集合已被强制转换为通用接口集合(IList或IEnumberable).

例如:

    private static int Sum(int[] array)
    {
        int sum = 0;

        foreach (int i in array)
            sum += i;

        return sum;
    }
Run Code Online (Sandbox Code Playgroud)

上面的代码执行速度明显快于下面的代码,其中参数更改为类型IList(或IEnumerable):

    private static int Sum(IList<int> array)
    {
        int sum = 0;

        foreach (int i in array)
            sum += i;

        return sum;
    }
Run Code Online (Sandbox Code Playgroud)

如果传递的对象是原始数组,并且如果我尝试将循环更改为for循环而不是foreach循环,则仍会出现性能损失.

我可以通过编码来解决性能问题:

    private static int Sum(IList<int> array)
    {
        int sum = 0;

        if( array is int[] )
            foreach (int i in (int[])array)
                sum += i;
        else
            foreach (int i in array)
                sum += i;

        return sum;
    }
Run Code Online (Sandbox Code Playgroud)

有没有更优雅的方法来解决这个问题?感谢您的时间.

编辑:我的基准代码:

    static void Main(string[] args)
    {
        int[] values = Enumerable.Range(0, 10000000).ToArray<int>();
        Stopwatch sw = new Stopwatch();

        sw.Start();
        Sum(values);
        //Sum((IList<int>)values);
        sw.Stop();

        Console.WriteLine("Elasped: {0} ms", sw.ElapsedMilliseconds);
        Console.Read();
    }
Run Code Online (Sandbox Code Playgroud)

Mag*_*tLU 19

如果此方法对性能至关重要,那么最好的选择是Sum使用int[]as作为参数创建重载.CLR的JIT可以检测数组上的foreach样式迭代,并且可以跳过范围检查并直接寻址每个元素.循环的每次迭代在x86上需要3-5条指令,只有一次内存查找.

使用IList时,JIT不了解底层集合的结构并最终使用IEnumerator<int>.循环的每次迭代使用两个接口调用 - 一个用于Current,一个用于MoveNext(2-3个内存查找和每个调用).这最有可能导致约20个非常昂贵的指令,你可以做的很少.

编辑:如果您对附带调试器的发布版本中JIT发出的实际机器代码感到好奇,请点击此处.

阵列版本

            int s = 0;
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  xor         esi,esi 
            foreach (int i in arg)
00000007  xor         edx,edx 
00000009  mov         edi,dword ptr [ecx+4] 
0000000c  test        edi,edi 
0000000e  jle         0000001B 
00000010  mov         eax,dword ptr [ecx+edx*4+8] 
                s += i;
00000014  add         esi,eax 
00000016  inc         edx  
            foreach (int i in arg)
00000017  cmp         edi,edx 
00000019  jg          00000010 
Run Code Online (Sandbox Code Playgroud)

IEnumerable版本

            int s = 0;
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  
00000006  sub         esp,1Ch 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-28h] 
0000000e  mov         ecx,6 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-18h],eax 
0000001e  xor         edx,edx 
00000020  mov         dword ptr [ebp-24h],edx 
            foreach (int i in arg)
00000023  call        dword ptr ds:[009E0010h] 
00000029  mov         dword ptr [ebp-28h],eax 
0000002c  mov         ecx,dword ptr [ebp-28h] 
0000002f  call        dword ptr ds:[009E0090h] 
00000035  test        eax,eax 
00000037  je          00000052 
00000039  mov         ecx,dword ptr [ebp-28h] 
0000003c  call        dword ptr ds:[009E0110h] 
                s += i;
00000042  add         dword ptr [ebp-24h],eax 
            foreach (int i in arg)
00000045  mov         ecx,dword ptr [ebp-28h] 
00000048  call        dword ptr ds:[009E0090h] 
0000004e  test        eax,eax 
00000050  jne         00000039 
00000052  mov         dword ptr [ebp-1Ch],0 
00000059  mov         dword ptr [ebp-18h],0FCh 
00000060  push        0F403BCh 
00000065  jmp         00000067 
00000067  cmp         dword ptr [ebp-28h],0 
0000006b  je          00000076 
0000006d  mov         ecx,dword ptr [ebp-28h] 
00000070  call        dword ptr ds:[009E0190h] 
Run Code Online (Sandbox Code Playgroud)