Доступ к памяти через указатели считается более эффективным, чем доступ к памяти через массив.
Это могло быть правдой в прошлом, когда компиляторы были относительно глупыми зверями. Вам нужно только взглянуть на часть кода, выведенного gcc
в режимах высокой оптимизации, чтобы понять, что это больше не так. Часть этого кода очень сложна для понимания, но, как только вы это сделаете, его яркость очевидна.
Приличный компилятор сгенерирует один и тот же код для доступа к указателям и массивам, и вам, вероятно, не стоит беспокоиться об этом уровне производительности. Люди, которые пишут компиляторы, знают гораздо больше об их целевых архитектурах, чем мы, простые смертные. Сконцентрируйтесь больше на макроуровне при оптимизации кода (выбор алгоритма и т. Д.) И доверьтесь создателям инструментов, которые делают свою работу.
На самом деле, я удивлен, что компилятор не оптимизировал весь
temp = a[0];
строка не существует, поскольку temp
переписано в следующей строке с другим значением, а a
никоим образом не помечено volatile
.
Я давно помню городской миф о эталоне для последнего компилятора VAX Fortran (показывающий мой возраст здесь), который превзошел своих конкурентов на несколько порядков.
Оказалось, что компилятор выяснил, что результат вычисления бенчмарка нигде не использовался, поэтому он оптимизировал весь цикл вычислений в забвение. Отсюда значительное улучшение скорости бега.
Обновление: Причина, по которой оптимизированный код более эффективен в вашем конкретном случае, заключается в том, как вы находите местоположение. a
будет в фиксированном месте, определенном во время соединения / загрузки, и ссылка на него будет исправлена в то же время. Так что a[0]
или даже a[any constant]
будет в фиксированном месте.
И p
также будет находиться в фиксированном месте по той же причине. Но *p
(содержимое p
) является переменной и, следовательно, потребуется дополнительный поиск, чтобы найти правильную ячейку памяти.
Вы, вероятно, обнаружите, что наличие еще одной переменной x
, установленной в 0 (не const
) и использование a[x]
, также приведет к дополнительным вычислениям.
В одном из ваших комментариев вы указываете:
Выполнение, как вы предложили, привело также к 3 инструкциям для доступа к памяти через массивы (выборочный индекс, выборочное значение элемента массива, сохранение в temp). Но я все еще не вижу эффективности. : - (
Мой ответ на это таков, что вы, вероятно, не увидите эффективность использования указателей. Современные компиляторы более чем способны выяснить, что операции с массивами и операции с указателями можно превратить в один и тот же базовый машинный код.
Фактически, без включенной оптимизации код указателя может быть менее эффективным. Рассмотрим следующие переводы:
int *pa, i, a[10];
for (i = 0; i < 10; i++)
a[i] = 100;
/*
movl $0, -16(%ebp) ; this is i, init to 0
L2:
cmpl $9, -16(%ebp) ; from 0 to 9
jg L3
movl -16(%ebp), %eax ; load i into register
movl $100, -72(%ebp,%eax,4) ; store 100 based on array/i
leal -16(%ebp), %eax ; get address of i
incl (%eax) ; increment
jmp L2 ; and loop
L3:
*/
for (pa = a; pa < a + 10; pa++)
*pa = 100;
/*
leal -72(%ebp), %eax
movl %eax, -12(%ebp) ; this is pa, init to &a[0]
L5:
leal -72(%ebp), %eax
addl $40, %eax
cmpl -12(%ebp), %eax ; is pa at &(a[10])
jbe L6 ; yes, stop
movl -12(%ebp), %eax ; get pa
movl $100, (%eax) ; store 100
leal -12(%ebp), %eax ; get pa
addl $4, (%eax) ; add 4 (sizeof int)
jmp L5 ; loop around
L6:
*/
Из этого примера вы можете видеть, что пример указателя длиннее, а излишне, поэтому . Он загружает pa
в %eax
несколько раз без его изменения и действительно чередуется %eax
между pa
и &(a[10])
. Оптимизация по умолчанию здесь вообще отсутствует.
Когда вы переходите на уровень оптимизации 2, вы получаете код:
xorl %eax, %eax
L5:
movl $100, %edx
movl %edx, -56(%ebp,%eax,4)
incl %eax
cmpl $9, %eax
jle L5
для версии массива и:
leal -56(%ebp), %eax
leal -16(%ebp), %edx
jmp L14
L16:
movl $100, (%eax)
addl $4, %eax
L14:
cmpl %eax, %edx
ja L16
для версии указателя.
Я не собираюсь делать анализ тактов здесь (поскольку это слишком много работы, и я в основном ленив), но я укажу одну вещь. В коде для обеих версий нет большой разницы с точки зрения инструкций ассемблера, и, учитывая скорость, с которой на самом деле работают современные процессоры, вы не заметите разницы, если не выполните миллиардов этих операций , Я всегда предпочитаю писать код для удобства чтения и беспокоиться о производительности, только если это становится проблемой.
В качестве отступления от того утверждения, на которое вы ссылаетесь:
5.3. Указатели и массивы: версия указателя, как правило, будет быстрее, но, по крайней мере, для непосвященных, сразу сложнее понять.
восходит к самым ранним версиям K & R, включая мою древнюю версию 1978 года, где функции еще пишутся:
getint(pn)
int *pn;
{
...
}
Компиляторы прошли очень долгий путь с тех пор.