Почему мой индекс массива быстрее указателя

Почему индекс массива быстрее указателя? Не предполагается ли указатель быстрее, чем индекс массива?

** я использовал time.h clock_t для тестирования двух функций, каждый цикл 2 миллиона раз.

Pointer time : 0.018995

Index time : 0.017864

void myPointer(int a[], int size)
{
     int *p;
     for(p = a; p < &a[size]; p++)
     {
         *p = 0;
     }
}


void myIndex(int a[], int size)
{
     int i;
     for(i = 0; i < size; i++)
     {
         a[i] = 0;
     }
}

Ответ 1

Нет, никогда не следует указывать указатели быстрее, чем индекс массива. Если один из кодов быстрее, чем другой, это в основном потому, что некоторые вычисления адресов могут отличаться. Вопрос также должен содержать информацию о флагах компилятора и оптимизации, поскольку это может сильно повлиять на производительность.

Индекс массива в вашем контексте (граница массива неизвестна) точно совпадает с операцией указателя. С точки зрения компиляторов это просто другое выражение арифметики указателя. Ниже приведен пример оптимизированного кода x86 в Visual Studio 2010 с полной оптимизацией и без встроенного.

     3: void myPointer(int a[], int size)
     4: {
013E1800  push        edi  
013E1801  mov         edi,ecx  
     5:      int *p;
     6:      for(p = a; p < &a[size]; p++)
013E1803  lea         ecx,[edi+eax*4]  
013E1806  cmp         edi,ecx  
013E1808  jae         myPointer+15h (13E1815h)  
013E180A  sub         ecx,edi  
013E180C  dec         ecx  
013E180D  shr         ecx,2  
013E1810  inc         ecx  
013E1811  xor         eax,eax  
013E1813  rep stos    dword ptr es:[edi]  
013E1815  pop         edi  
     7:      {
     8:          *p = 0;
     9:      }
    10: }
013E1816  ret 

    13: void myIndex(int a[], int size)
    14: {
    15:      int i;
    16:      for(i = 0; i < size; i++)
013E17F0  test        ecx,ecx  
013E17F2  jle         myIndex+0Ch (13E17FCh)  
013E17F4  push        edi  
013E17F5  xor         eax,eax  
013E17F7  mov         edi,edx  
013E17F9  rep stos    dword ptr es:[edi]  
013E17FB  pop         edi  
    17:      {
    18:          a[i] = 0;
    19:      }
    20: }
013E17FC  ret 

С первого взгляда myIndex выглядит быстрее, потому что количество инструкций меньше, однако две части кода по существу одинаковы. Оба в конечном итоге используют rep stos, который является инструкцией x86-повторения (цикла). Единственное отличие состоит в том, что вычисление границы цикла. Цикл for в myIndex имеет счетчик опроса size как есть (т.е. Не требуется никаких вычислений). Но, myPointer нуждается в некотором вычислении, чтобы получить счетчик поездки цикла for. Это единственное различие. Важные операции цикла одинаковы. Таким образом, разница незначительна.

Подводя итог, производительность myPointer и myIndex в оптимизированном коде должна быть идентичной.


FYI, если граница массива известна во время компиляции, например, int A[constant_expression], тогда обращения к этому массиву могут быть намного быстрее, чем указатель. Это связано главным образом с тем, что доступ к массиву свободен от анализатора указателей. Компиляторы могут отлично вычислять информацию о зависимостях вычислений и доступов в массиве фиксированного размера, поэтому он может выполнять расширенные оптимизации, включая автоматическую распараллеливание.

Однако, если вычисления основаны на указателях, компиляторы должны выполнить анализ указателей для дальнейшей оптимизации, что в значительной степени ограничено в C/С++. Обычно он заканчивается консервативными результатами анализа указателей и дает несколько возможностей оптимизации.

Ответ 2

Это может быть сравнение в цикле for, которое вызывает разницу. Условие терминации проверяется на каждой итерации, и ваш пример "указатель" имеет несколько более сложное условие завершения (с адресом & a [size]). Поскольку & a [размер] не изменяется, вы можете попробовать установить его в переменную, чтобы не перерасчитывать ее на каждой итерации цикла.

Ответ 3

Разрывы в массиве p[i] составляют *(p + i). Компиляторы используют инструкции, которые выполняют математику + разыменование в 1 или 2 циклах (например, инструкцию x86 LEA) для оптимизации скорости.

С контуром указателя он разделяет доступ и смещение на отдельные части, и компилятор не может его оптимизировать.

Ответ 4

К сожалению, на моей 64-битной системе результаты совершенно разные. У меня есть это

 int i;

 for(i = 0; i < size; i++)
 {
     *(a+i) = 0;
 }

около 100 раз! медленнее, чем этот

 int i;
 int * p = a;

 for(i = 0; i < size; i++)
 {
     *(p++) = 0;
 }

при компиляции с -O3. Это подсказывает мне, что переход к следующему адресу намного проще для 64-битного процессора, чем для вычисления адреса назначения с некоторого смещения. Но я не уверен.

EDIT:. Это действительно связано с 64-битной архитектурой, потому что тот же код с одинаковыми флагами компиляции не показывает реальной разницы в производительности в 32-разрядной системе.

Ответ 5

Время так близко друг к другу, что, если вы делали их неоднократно, вы не можете видеть большую часть разницы. Оба сегмента кода составляют одну и ту же сборку. По определению нет разницы.

Ответ 6

Я бы предложил запустить каждый цикл 200 миллионов раз, а затем запустить каждый цикл 10 раз и выполнить самые быстрые измерения. Это будет учитывать эффекты от планирования ОС и так далее.

Я бы предложил вам разобрать код для каждого цикла.

Ответ 7

Похоже, что индексное решение может сохранить несколько инструкций с помощью сравнения в цикле for.

Ответ 8

Доступ к данным через индекс массива или указатель в точности эквивалентен. Пройдите через следующую программу со мной...

Есть цикл, который продолжается до 100 раз, но когда мы видим код дизассемблирования, что есть данные, к которым мы обращаемся, имеет минимальную сопоставимость команд для доступа через массив Index

Но это не значит, что доступ к данным через указатель быстрый на самом деле зависит от команды, выполняемой компилятором. Но указатель и индекс массива, используемые массивом адресов, получают доступ к значению из смещения и увеличивают его, а указатель имеет адрес,

int a[100];
fun1(a,100);
fun2(&a[0],5);
}
void fun1(int a[],int n)
{
int i;
for(i=0;i<=99;i++)
{
a[i]=0;
printf("%d\n",a[i]);
}
}
void fun2(int *p,int n)
{
int i;
for(i=0;i<=99;i++)
{
*p=0;
printf("%d\n",*(p+i));
}
}


disass fun1
Dump of assembler code for function fun1:
   0x0804841a <+0>: push   %ebp
   0x0804841b <+1>: mov    %esp,%ebp
   0x0804841d <+3>: sub    $0x28,%esp`enter code here`
   0x08048420 <+6>: movl   $0x0,-0xc(%ebp)
   0x08048427 <+13>:    jmp    0x8048458 <fun1+62>
   0x08048429 <+15>:    mov    -0xc(%ebp),%eax
   0x0804842c <+18>:    shl    $0x2,%eax
   0x0804842f <+21>:    add    0x8(%ebp),%eax
   0x08048432 <+24>:    movl   $0x0,(%eax)
   0x08048438 <+30>:    mov    -0xc(%ebp),%eax
   0x0804843b <+33>:    shl    $0x2,%eax
   0x0804843e <+36>:    add    0x8(%ebp),%eax
   0x08048441 <+39>:    mov    (%eax),%edx
   0x08048443 <+41>:    mov    $0x8048570,%eax
   0x08048448 <+46>:    mov    %edx,0x4(%esp)
   0x0804844c <+50>:    mov    %eax,(%esp)
   0x0804844f <+53>:    call   0x8048300 <[email protected]>
   0x08048454 <+58>:    addl   $0x1,-0xc(%ebp)
   0x08048458 <+62>:    cmpl   $0x63,-0xc(%ebp)
   0x0804845c <+66>:    jle    0x8048429 <fun1+15>
   0x0804845e <+68>:    leave  
   0x0804845f <+69>:    ret    
End of assembler dump.
(gdb) disass fun2
Dump of assembler code for function fun2:
   0x08048460 <+0>: push   %ebp
   0x08048461 <+1>: mov    %esp,%ebp
   0x08048463 <+3>: sub    $0x28,%esp
   0x08048466 <+6>: movl   $0x0,-0xc(%ebp)
   0x0804846d <+13>:    jmp    0x8048498 <fun2+56>
   0x0804846f <+15>:    mov    0x8(%ebp),%eax
   0x08048472 <+18>:    movl   $0x0,(%eax)
   0x08048478 <+24>:    mov    -0xc(%ebp),%eax
   0x0804847b <+27>:    shl    $0x2,%eax
   0x0804847e <+30>:    add    0x8(%ebp),%eax
   0x08048481 <+33>:    mov    (%eax),%edx
   0x08048483 <+35>:    mov    $0x8048570,%eax
   0x08048488 <+40>:    mov    %edx,0x4(%esp)
   0x0804848c <+44>:    mov    %eax,(%esp)
   0x0804848f <+47>:    call   0x8048300 <[email protected]>
   0x08048494 <+52>:    addl   $0x1,-0xc(%ebp)
   0x08048498 <+56>:    cmpl   $0x63,-0xc(%ebp)
   0x0804849c <+60>:    jle    0x804846f <fun2+15>
   0x0804849e <+62>:    leave  
   0x0804849f <+63>:    ret    
End of assembler dump.
(gdb)