Вам необходимо понять причину этого требования. Вы когда-нибудь задавались вопросом, почему это быстрее? Давайте сравним код:
int i;
int a[20];
// Init all values to zero
memset(a, 0, sizeof(a));
for (i = 0; i < 20; i++) {
printf("Value of %d is %d\n", i, a[i]);
}
Все они равны нулю, какой сюрприз :-P Вопрос в том, что означает a[i]
на самом деле в машинном коде низкого уровня? Это значит
Взять в память адрес a
.
Добавьте i
размер отдельного элемента a
к этому адресу (обычно это четыре байта).
Получить значение с этого адреса.
Таким образом, каждый раз, когда вы выбираете значение из a
, базовый адрес a
добавляется к результату умножения i
на четыре. Если вы просто разыменовываете указатель, шаги 1. и 2. выполнять не нужно, только шаг 3.
Рассмотрите код ниже.
int i;
int a[20];
int * b;
memset(a, 0, sizeof(a));
b = a;
for (i = 0; i < 20; i++) {
printf("Value of %d is %d\n", i, *b);
b++;
}
Этот код может быть быстрее ... но даже если это так, разница крошечная. Почему это может быть быстрее? «* b» соответствует шагу 3. выше. Однако «b ++» отличается от шага 1. и шага 2. «b ++» увеличит указатель на 4.
( важно для новичков : работает ++
на указатель не увеличит
указатель одного байта в памяти! Будет
увеличить указатель на столько байтов
в памяти, как данные, на которые он указывает,
по размеру. Это указывает на int
и
int
- это четыре байта на моей машине, поэтому b ++
увеличивает b на четыре!)
Хорошо, но почему это может быть быстрее? Потому что добавление четырех к указателю быстрее, чем умножение i
на четыре и добавление этого к указателю. У вас есть дополнение в любом случае, но во втором у вас нет умножения (вы избегаете процессорного времени, необходимого для одного умножения). Учитывая скорость современных процессоров, даже если бы массив составлял 1 млн. Элементов, я хотел бы знать, не могли бы вы действительно сравнить разницу.
То, что современный компилятор может оптимизировать любой из них так, чтобы он был одинаково быстрым, - это то, что вы можете проверить, посмотрев выходные данные сборки, которые он производит. Это можно сделать, передав опцию "-S" (заглавная S) в GCC.
Вот код первого кода C (использовался уровень оптимизации -Os
, что означает оптимизацию по размеру и скорости кода, но не выполняйте оптимизацию по скорости, которая заметно увеличит размер кода, в отличие от -O2
и очень сильно отличается -O3
):
_main:
pushl %ebp
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
subl $108, %esp
call ___i686.get_pc_thunk.bx
"L00000000001$pb":
leal -104(%ebp), %eax
movl $80, 8(%esp)
movl $0, 4(%esp)
movl %eax, (%esp)
call L_memset$stub
xorl %esi, %esi
leal LC0-"L00000000001$pb"(%ebx), %edi
L2:
movl -104(%ebp,%esi,4), %eax
movl %eax, 8(%esp)
movl %esi, 4(%esp)
movl %edi, (%esp)
call L_printf$stub
addl $1, %esi
cmpl $20, %esi
jne L2
addl $108, %esp
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
То же самое со вторым кодом:
_main:
pushl %ebp
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
subl $124, %esp
call ___i686.get_pc_thunk.bx
"L00000000001$pb":
leal -104(%ebp), %eax
movl %eax, -108(%ebp)
movl $80, 8(%esp)
movl $0, 4(%esp)
movl %eax, (%esp)
call L_memset$stub
xorl %esi, %esi
leal LC0-"L00000000001$pb"(%ebx), %edi
L2:
movl -108(%ebp), %edx
movl (%edx,%esi,4), %eax
movl %eax, 8(%esp)
movl %esi, 4(%esp)
movl %edi, (%esp)
call L_printf$stub
addl $1, %esi
cmpl $20, %esi
jne L2
addl $124, %esp
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
Ну, это другое, это точно. Разница чисел 104 и 108 исходит от переменной b
(в первом коде на стеке была одна переменная меньше, теперь у нас есть еще одна, меняющая адреса стека). Реальная разница в коде в цикле for
составляет
movl -104(%ebp,%esi,4), %eax
по сравнению с
movl -108(%ebp), %edx
movl (%edx,%esi,4), %eax
На самом деле, мне кажется, что первый подход более быстрый (!), Поскольку он выполняет один машинный код ЦП для выполнения всей работы (ЦПУ делает все это за нас) вместо двух машинных кодов. С другой стороны, две приведенные ниже команды сборки могут иметь меньшее время выполнения, чем приведенная выше.
В качестве заключительного слова я бы сказал, что в зависимости от вашего компилятора и возможностей ЦП (какие команды предлагают ЦП для доступа к памяти каким-либо образом), результат может быть в любом случае. Любой из них может быть быстрее / медленнее. Вы не можете сказать наверняка, если не ограничитесь только одним компилятором (имеется в виду также одна версия) и одним конкретным процессором. Поскольку процессоры могут делать все больше и больше в одной команде сборки (давным-давно, компилятору действительно приходилось вручную извлекать адрес, умножать i
на четыре и складывать оба вместе перед извлечением значения), операторы, которые раньше были абсолютной истиной века назад в настоящее время все более и более сомнительным. Также кто знает, как внутренне работают процессоры? Выше я сравниваю одну инструкцию по сборке с двумя другими.
Я вижу, что количество инструкций различно, и время, в которое такие инструкции могут быть разными, также может быть разным. Кроме того, сколько памяти нужно этим инструкциям в их машинном представлении (в конце концов, они должны быть перенесены из памяти в кэш ЦП). Однако современные процессоры не выполняют инструкции так, как вы их кормите. Разделение больших команд (часто называемых CISC) на маленькие подинструкции (часто называемые RISC), что также позволяет им лучше оптимизировать поток программ для внутренней скорости. Фактически, первая отдельная инструкция и две другие инструкции, приведенные ниже, могут привести к одинаковому набору подинструкций , и в этом случае не будет никакой измеримой разницы в скорости.
Что касается Objective-C, то это просто C с расширениями. Таким образом, все, что справедливо для C, будет справедливо и для Objective-C с точки зрения указателей и массивов. Если вы используете Объекты с другой стороны (например, NSArray
или NSMutableArray
), это совершенно другой зверь. Однако в этом случае вы должны в любом случае обращаться к этим массивам с помощью методов, поэтому нет доступа к указателю / массиву.