Единственный способ узнать наверняка — это проверить. Я должен согласиться с тем, что потребуется довольно умный компилятор для получения эффективного вывода для:
if(n%2) // or (n%2==0) and flip the order
n=n-1
else
n=n+1
как это могло бы быть для n ^= 1;
, но я не проверял ничего подобного в последнее время достаточно, чтобы сказать с какой-либо уверенностью.
Что касается вашего второго вопроса, я сомневаюсь, что это имеет какое-либо значение - сравнение равенства быстро закончится для любого из этих методов. Если вам нужна скорость, главное избегать использования ветки вообще - например. что-то типа:
if (a == b)
c += d;
можно записать как: c += d * (a==b);
. Глядя на язык ассемблера, второй часто будет выглядеть немного запутанным (с уродливым хламом, чтобы получить результат сравнения из флагов в обычный регистр), но все же часто работает лучше, избегая каких-либо ветвей.
Изменить: по крайней мере компиляторы, которые у меня есть (gcc и MSVC), не генерируют cmov
для if
, но они генерируют sete
для * (a==b)
. Я расширил код до чего-то проверяемого.
Edit2: Поскольку Potatoswatter выдвинул еще одну возможность, используя побитовое и вместо умножения, я решил проверить это вместе с другими. Вот код с добавленным:
#include <time.h>
#include <iostream>
#include <stdlib.h>
int addif1(int a, int b, int c, int d) {
if (a==b)
c+=d;
return c;
}
int addif2(int a, int b, int c, int d) {
return c += d * (a == b);
}
int addif3(int a, int b, int c, int d) {
return c += d & -(a == b);
}
int main() {
const int iterations = 50000;
int x = rand();
unsigned tot1 = 0;
unsigned tot2 = 0;
unsigned tot3 = 0;
clock_t start1 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot1 +=addif1(i, j, i, x);
}
clock_t stop1 = clock();
clock_t start2 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot2 +=addif2(i, j, i, x);
}
clock_t stop2 = clock();
clock_t start3 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot3 +=addif3(i, j, i, x);
}
clock_t stop3 = clock();
std::cout << "Ignore: " << tot1 << "\n";
std::cout << "Ignore: " << tot2 << "\n";
std::cout << "Ignore: " << tot3 << "\n";
std::cout << "addif1: " << stop1-start1 << "\n";
std::cout << "addif2: " << stop2-start2 << "\n";
std::cout << "addif3: " << stop3-start3 << "\n";
return 0;
}
Теперь самое интересное: результаты для третьей версии довольно интересны. Для MS VC++ мы получаем примерно то, что, вероятно, ожидает большинство из нас:
Ignore: 2682925904
Ignore: 2682925904
Ignore: 2682925904
addif1: 4814
addif2: 3504
addif3: 3021
Использование &
вместо *
дает определенное улучшение -- почти такое же улучшение, какое дает *
по сравнению с if
. Однако с gcc результат немного отличается:
Ignore: 2680875904
Ignore: 2680875904
Ignore: 2680875904
addif1: 2901
addif2: 2886
addif3: 7675
В этом случае код, использующий if
, намного ближе по скорости к коду, использующему *
, но код, использующий &
, медленнее любого из них — намного медленнее! Если кого-то это волнует, я нашел это настолько удивительным, что пару раз перекомпилировал с разными флагами, несколько раз перезапустил с каждым и т. помедленнее.
Плохой результат с третьей версией кода, скомпилированного с помощью gcc, возвращает нас к тому, с чего я сказал начать [и заканчивает это редактирование]:
Как я сказал для начала, «единственный способ узнать наверняка — это проверить» — но, по крайней мере, в этом ограниченном тестировании умножение постоянно превосходит if
. Может существовать некоторая комбинация компилятора, флагов компилятора, ЦП, шаблона данных, количества итераций и т. д., которая отдает предпочтение if
, а не умножению — нет никаких сомнений в том, что разница достаточно мала, чтобы тест движение в другом направлении вполне правдоподобно. Тем не менее, я считаю, что эту технику стоит знать; для основных компиляторов и процессоров он кажется достаточно эффективным (хотя он, безусловно, больше полезен с MSVC, чем с gcc).
[возобновление редактирования2:] результат с gcc с использованием &
демонстрирует степень, в которой 1) микрооптимизация может быть специфичной для компилятора и 2) насколько реальные результаты могут отличаться от ожиданий.
person
Jerry Coffin
schedule
22.02.2010
if (!(n_1 ^ n_2) )
иif (n_1 ^ n_1) else
могут отличаться. Играйте с вещами по своему усмотрению, но проверяйте производительность. Кроме того, выполняйте подобные микрооптимизации только в том случае, если вы профилировали и обнаружили, что рассматриваемый код выполняется достаточно часто, чтобы с ним можно было заморачиваться. - person David Thornley   schedule 22.02.2010