在C#中,引用数组变量是否较慢?

Ozz*_*zah 19 .net c# arrays optimization performance

我有一个整数数组,我正在循环它们:

for (int i = 0; i < data.Length; i++)
{
  // do a lot of stuff here using data[i]
}
Run Code Online (Sandbox Code Playgroud)

如果我做:

for (int i = 0; i < data.Length; i++)
{
  int value = data[i];
  // do a lot of stuff with value instead of data[i]
}
Run Code Online (Sandbox Code Playgroud)

是否有任何表现收益/损失?

根据我的理解,直接访问C/C++数组元素,即整数的n元素数组具有长度为n*sizeof(int)的连续内存块,而程序访问元素i通过执行类似*data [i]的操作=*data [0] +(i*sizeof(int)).(请原谅我滥用符号,但你理解我的意思.)

所以这意味着C/C++在引用数组变量时应该没有性能增益/损失.

C#怎么样?C#有一堆额外的开销,如data.Length,data.IsSynchronized,data.GetLowerBound(),data.GetEnumerator().

显然,C#数组与C/C++数组不同.

那么判决是什么?我应该存储int value = data [i]并使用值,还是没有性能影响?

Han*_*ant 23

你可以吃蛋糕,也可以吃.在许多情况下,抖动优化器可以轻松确定数组索引访问是安全的并且不需要检查.你问题中的任何for循环就是这样一种情况,jitter知道索引变量的范围.并且知道再次检查它是没有意义的.

您可以看到的唯一方法是生成的机器代码.我将给出一个带注释的例子:

    static void Main(string[] args) {
        int[] array = new int[] { 0, 1, 2, 3 };
        for (int ix = 0; ix < array.Length; ++ix) {
            int value = array[ix];
            Console.WriteLine(value);
        }
    }

Starting at the for loop, ebx has the pointer to the array:

            for (int ix = 0; ix < array.Length; ++ix) {
00000037  xor         esi,esi                       ; ix = 0
00000039  cmp         dword ptr [ebx+4],0           ; array.Length < 0 ?
0000003d  jle         0000005A                      ; skip everything
                int value = array[ix];
0000003f  mov         edi,dword ptr [ebx+esi*4+8]   ; NO BOUNDS CHECK !!!
                Console.WriteLine(value);
00000043  call        6DD5BE38                      ; Console.Out
00000048  mov         ecx,eax                       ; arg = Out
0000004a  mov         edx,edi                       ; arg = value
0000004c  mov         eax,dword ptr [ecx]           ; call WriteLine()
0000004e  call        dword ptr [eax+000000BCh] 
            for (int ix = 0; ix < array.Length; ++ix) {
00000054  inc         esi                           ; ++ix
00000055  cmp         dword ptr [ebx+4],esi         ; array.Length > ix ?
00000058  jg          0000003F                      ; loop
Run Code Online (Sandbox Code Playgroud)

数组索引发生在地址00003f,ebx具有数组指针,esi是索引,8是对象中数组元素的偏移量.请注意如何不再针对数组边界检查esi值.它的运行速度与C编译器生成的代码一样快.


Meh*_*dad 15

是的,由于每次访问阵列的边界检查都会导致性能下降.

,你很可能不需要担心它.

是的,您可以存储该值并使用该值.不,这不是因为性能问题,而是因为它使代码更具可读性(恕我直言).


顺便说一句,JIT编译器可能会优化冗余检查,因此并不意味着您实际上会检查每次调用.不管怎样,你可能不值得花时间去担心它; 只是使用它,如果它成为一个瓶颈,你总是可以回去使用unsafe块.

  • 因为真正的每一个优秀的优化器(至少对于像Java或C#这样的语言来说,这不是普遍的事实)都会进行常见的子表达式消除,这将消除性能损失.并且在指定的代码中,无论如何都将使用检查. (3认同)