О единственном способе узнать наверняка, это проверить. Я должен согласиться с тем, что потребуется достаточно умный компилятор для получения максимально эффективного вывода:
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).
[возобновление edit2:] результат с gcc с использованием &
демонстрирует степень, в которой 1) микрооптимизации могут быть / являются специфичными для компилятора, и 2) насколько реальные результаты могут отличаться от ожиданий.