Что компилируется в более быстрый код: «n * 3» или «n + (n * 2)»?

Что компилируется в более быстрый код: «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);

Или это то, о чем не нужно беспокоиться, поскольку оптимизирующие компиляторы, вероятно, это учтут в любом случае?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
1 828
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Ответ принят как подходящий

IMO такая микро-оптимизация не нужна, если вы не работаете с каким-то экзотическим компилятором. Я бы поставил удобочитаемость на первое место.

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

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

Большинство компиляторов достаточно умны, чтобы разложить целочисленное умножение на серию битовых сдвигов и сложений. Я не знаю о компиляторах Windows, но, по крайней мере, с помощью gcc вы можете заставить его выплевывать ассемблер, и если вы посмотрите на него, вы, вероятно, увидите идентичный ассемблер для обоих способов его написания.

Компиляторы хороши в оптимизации кода, такого как ваш. Любой современный компилятор будет выдавать один и тот же код для обоих случаев и дополнительно заменять * 2 сдвигом влево.

Не будьте уверены :) Я видел очень странные компиляторы для разработки встраиваемого ПО.

aku 10.09.2008 14:58

Во встраиваемых системах почти все расхожее мнение заканчивается. ;-)

Konrad Rudolph 10.09.2008 15:12

Это будет зависеть от компилятора, его конфигурации и окружающего кода.

Вы не должны пытаться угадывать, идет ли дело «быстрее», не проводя замеров.

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

Так как это легко измерить самостоятельно, почему бы этого не сделать? (Используя 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» дает лучший результат.

Adam Rosenfield 20.11.2008 04:10

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

DarenW 27.12.2009 03:25

Неважно. Современные процессоры могут выполнять целочисленную инструкцию MUL за один такт или меньше, в отличие от старых процессоров, которым для выполнения MUL требовалось выполнять серию сдвигов и внутренних суммирований, тем самым используя несколько циклов. Готов поспорить, что

MUL EAX,3

выполняется быстрее, чем

MOV EBX,EAX
SHL EAX,1
ADD EAX,EBX

Последним процессором, в котором такая оптимизация могла быть полезна, был, вероятно, 486. (да, это связано с процессорами Intel, но, вероятно, также характерно для других архитектур).

В любом случае любой разумный компилятор должен уметь генерировать самый маленький / самый быстрый код. Так что всегда сначала ориентируйтесь на удобочитаемость.

Я действительно сомневаюсь, что MUL выполняется быстрее, если учесть задержку и негибкость того, какие регистры вы можете использовать. Более того, на x86 LEA, а не последовательность из трех инструкций, которую вы указали, будет использоваться любым достойным компилятором как для 3 * n, так и для n + 2 * n.

R.. GitHub STOP HELPING ICE 01.07.2011 06:26

Верно, но LEA полезен только при умножении на небольшой набор констант (2, 3, 4, 5, 8 и 9, если я правильно помню). В любом случае я хотел позволить компилятору вычислить самый быстрый код.

Ferruccio 01.08.2011 21:21

Нетрудно выяснить, что компилятор делает с вашим кодом (здесь я использую 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 внутри за один шаг.

Artelius 07.01.2009 00:55

Доверьте своему компилятору оптимизацию таких небольших фрагментов кода. Читаемость гораздо важнее на уровне кода. Настоящая оптимизация должна происходить на более высоком уровне.

Это не волнует. Думаю, есть более важные вещи, которые нужно оптимизировать. Сколько времени вы потратили на размышления и написание этого вопроса вместо того, чтобы самостоятельно кодировать и тестировать?

:-)

Если вы используете достойный оптимизирующий компилятор, просто написать код, который легко понять компилятору. Это упрощает компилятору выполнение умных оптимизаций.

Если вы задаете этот вопрос, значит, оптимизирующий компилятор знает об оптимизации больше, чем вы. Так что доверяйте компилятору. Используйте n * 3.

Взгляните также на этот ответ.

Другие вопросы по теме