在c#中,引用数组变量会变慢吗?

本文关键字:变量 数组 引用 | 更新日期: 2023-09-27 17:49:22

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

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

如果我这样做:

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

是否有性能增益/损失?

根据我的理解,C/c++数组元素是直接访问的,即一个n元素的整数数组有一个长度为n * sizeof(int)的连续内存块,程序通过做类似于*data[i] = *data[0] + (i * sizeof(int))的事情来访问元素i。(请原谅我滥用符号,但你明白我的意思。)

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

那么c#呢?c#有很多额外的开销,比如数据。长度,数据。IsSynchronized, data.GetLowerBound(), data.GetEnumerator().

显然,c#数组与C/c++数组是不一样的。

结果如何?我应该存储int value = data[I]并使用value,还是没有性能影响?

在c#中,引用数组变量会变慢吗?

你可以把蛋糕吃了。在许多情况下,抖动优化器可以很容易地确定数组索引访问是安全的,不需要检查。问题中的for循环就是这样一种情况,抖动知道索引变量的范围。并且知道再检查一次是没有意义的。

您可以从生成的机器码中看到它的唯一方法。我将给出一个带注释的示例:

    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

数组索引发生在地址00003f处,ebx是数组指针,esi是索引,8是对象中数组元素的偏移量。请注意,esi值不会根据数组边界再次进行检查。这和C编译器生成的代码一样快。

,由于每次访问数组时都进行边界检查,因此存在性能损失。

没有,你很可能不需要担心它。

是的,您应该存储该值并使用该值。不,这不是因为性能问题,而是因为它使代码更具可读性(IMHO)。


顺便说一下,JIT编译器可能会优化掉多余的检查,所以这并不意味着您实际上会对每个调用进行检查。不管怎样,这都不值得你花时间去担心;只要使用它,如果它最终成为瓶颈,您可以随时返回并使用unsafe块。

两种写法都有。双向运行,测量。然后你就知道了。

但是我认为你更喜欢使用副本而不是总是直接使用数组元素,因为这样更容易编写代码,特别是当你有很多操作涉及到特定的值

编译器只能在这里执行公共子表达式优化,如果它可以证明数组没有被其他线程或循环内调用的任何方法(包括委托)访问,则可能最好自己创建本地副本。

但是可读性应该是你主要关心的,除非这个循环执行了很多次。

所有这些在C和c++中也是正确的——索引数组将比访问局部变量慢。

作为旁注,你建议的优化是不好的:value是一个关键字,选择一个不同的变量名

不太确定,但如果要多次使用该值,存储该值可能不会有什么坏处。您还可以使用foreach语句:)