Что компилируется в более быстрый код: «ans = n * 3» или «ans = n + (n * 2)»?
Предполагая, что n - это либо int, либо long, и он работает на современном компьютере Win32 Intel.
Было бы иначе, если бы было задействовано какое-то разыменование, то есть какое из них было бы быстрее?
long a; long *pn; long ans; ... *pn = some_number; ans = *pn * 3;
Или же
ans = *pn+(*pn*2);
Или это то, о чем не нужно беспокоиться, поскольку оптимизирующие компиляторы, вероятно, это учтут в любом случае?





IMO такая микро-оптимизация не нужна, если вы не работаете с каким-то экзотическим компилятором. Я бы поставил удобочитаемость на первое место.
Это действительно зависит от используемого вами компилятора, но очень вероятно, что они переводятся в один и тот же код.
Вы можете проверить это самостоятельно, создав небольшую тестовую программу и проверив ее разборку.
Большинство компиляторов достаточно умны, чтобы разложить целочисленное умножение на серию битовых сдвигов и сложений. Я не знаю о компиляторах Windows, но, по крайней мере, с помощью gcc вы можете заставить его выплевывать ассемблер, и если вы посмотрите на него, вы, вероятно, увидите идентичный ассемблер для обоих способов его написания.
Компиляторы хороши в оптимизации кода, такого как ваш. Любой современный компилятор будет выдавать один и тот же код для обоих случаев и дополнительно заменять * 2 сдвигом влево.
Во встраиваемых системах почти все расхожее мнение заканчивается. ;-)
Это будет зависеть от компилятора, его конфигурации и окружающего кода.
Вы не должны пытаться угадывать, идет ли дело «быстрее», не проводя замеров.
В целом, вам не следует беспокоиться об этом виде оптимизации в наномасштабе в настоящее время - это почти всегда совершенно неуместно, и если бы вы действительно работали в области, где это имело значение, вы бы уже использовали профилировщик и смотрели на вывод на языке ассемблера компилятор.
Так как это легко измерить самостоятельно, почему бы этого не сделать? (Используя gcc и time из cygwin)
/* test1.c */
int main()
{
int result = 0;
int times = 1000000000;
while (--times)
result = result * 3;
return result;
}
machine:~$ gcc -O2 test1.c -o test1
machine:~$ time ./test1.exe
real 0m0.673s
user 0m0.608s
sys 0m0.000s
Сделайте тест пару раз и повторите для другого случая.
Если вы хотите взглянуть на код сборки, gcc -S -O2 test1.c
К сожалению, это плохой пример - с i686-apple-darwin8-gcc-4.0.1 он полностью удаляет «result = result * 3» из цикла, поскольку он всегда равен нулю. Изменение начального условия на «результат = 1» дает лучший результат.
или лучше создать массив случайных чисел и обработать его, чтобы компилятор не мог делать никаких предположений.
Неважно. Современные процессоры могут выполнять целочисленную инструкцию MUL за один такт или меньше, в отличие от старых процессоров, которым для выполнения MUL требовалось выполнять серию сдвигов и внутренних суммирований, тем самым используя несколько циклов. Готов поспорить, что
MUL EAX,3
выполняется быстрее, чем
MOV EBX,EAX
SHL EAX,1
ADD EAX,EBX
Последним процессором, в котором такая оптимизация могла быть полезна, был, вероятно, 486. (да, это связано с процессорами Intel, но, вероятно, также характерно для других архитектур).
В любом случае любой разумный компилятор должен уметь генерировать самый маленький / самый быстрый код. Так что всегда сначала ориентируйтесь на удобочитаемость.
Я действительно сомневаюсь, что MUL выполняется быстрее, если учесть задержку и негибкость того, какие регистры вы можете использовать. Более того, на x86 LEA, а не последовательность из трех инструкций, которую вы указали, будет использоваться любым достойным компилятором как для 3 * n, так и для n + 2 * n.
Верно, но LEA полезен только при умножении на небольшой набор констант (2, 3, 4, 5, 8 и 9, если я правильно помню). В любом случае я хотел позволить компилятору вычислить самый быстрый код.
Нетрудно выяснить, что компилятор делает с вашим кодом (здесь я использую DevStudio 2005). Напишите простую программу со следующим кодом:
int i = 45, j, k;
j = i * 3;
k = i + (i * 2);
Поместите точку останова в среднюю строку и запустите код с помощью отладчика. Когда сработает точка останова, щелкните правой кнопкой мыши исходный файл и выберите «Перейти к разборке». Теперь у вас будет окно с кодом, который выполняет ЦП. В этом случае вы заметите, что последние две строки производят точно такие же инструкции, а именно «lea eax, [ebx + ebx * 2]» (не сдвиг и добавление битов в данном конкретном случае). На современном ЦП IA32, вероятно, более эффективно выполнить прямой MUL, чем сдвиг бит, из-за конвейерной природы ЦП, которая влечет за собой штраф при слишком раннем использовании измененного значения.
Это демонстрирует, о чем говорит aku, а именно, что компиляторы достаточно умны, чтобы выбрать лучшие инструкции для вашего кода.
Я не понимаю, что трубопровод - проблема. Арифметический блок, вероятно, может обрабатывать ebx + ebx * 2 внутри за один шаг.
Доверьте своему компилятору оптимизацию таких небольших фрагментов кода. Читаемость гораздо важнее на уровне кода. Настоящая оптимизация должна происходить на более высоком уровне.
Это не волнует. Думаю, есть более важные вещи, которые нужно оптимизировать. Сколько времени вы потратили на размышления и написание этого вопроса вместо того, чтобы самостоятельно кодировать и тестировать?
:-)
Если вы используете достойный оптимизирующий компилятор, просто написать код, который легко понять компилятору. Это упрощает компилятору выполнение умных оптимизаций.
Если вы задаете этот вопрос, значит, оптимизирующий компилятор знает об оптимизации больше, чем вы. Так что доверяйте компилятору. Используйте n * 3.
Взгляните также на этот ответ.
Не будьте уверены :) Я видел очень странные компиляторы для разработки встраиваемого ПО.