Рассмотрим следующий пример:
template<int X> class MyClass
{
public:
MyClass(int x) {_ncx = x;}
void test()
{
for (unsigned int i = 0; i < 1000000; ++i) {
if ((X < 0) ? (_cx > 5) : (_ncx > 5)) {
/* SOMETHING */
} else {
/* SOMETHING */
}
}
}
protected:
static const int _cx = (X < 0) ? (-X) : (X);
int _ncx;
};
Мой вопрос: будет ли MyClass<-6> :: test () и MyClass<6> :: test () имеют другую скорость?
Я надеюсь на это, потому что в случае отрицательного параметра шаблона if
в тестовой функции может быть оценена во время компиляции, но я не уверен, каково поведение компилятора, если в троичном операторе есть вещь времени компиляции и вещь не компиляции (что имеет место здесь ).
Примечание: это чисто «теоретический» вопрос. Если вероятность «да» не равна нулю, я реализую некоторый класс для моего кода с такими параметрами шаблона во время компиляции, а если нет, я буду предоставлять только версии во время выполнения.
Для моего компилятора (clang ++ v2.9 на OS X) компилируем этот похожий, но не идентичный код:
void foo();
void bar();
template<int N>
void do_something( int arg ) {
if ( N<0 && arg<0 ) { foo(); }
else { bar(); }
}
// Some functions to instantiate the templates.
void one_fn(int arg) {
do_something<1>(arg);
}
void neg_one_fn(int arg) {
do_something<-1>(arg);
}
Это создает следующую сборку с clang++ -S -O3
,
Первая сборка функций явно имеет только bar
,
.globl __Z6one_fni
.align 4, 0x90
__Z6one_fni: ## @_Z6one_fni
Leh_func_begin0:
pushl %ebp
movl %esp, %ebp
popl %ebp
jmp __Z3barv ## TAILCALL
Leh_func_end0:
Вторая функция была уменьшена до простой, если вызвать bar
или же foo
,
.globl __Z10neg_one_fni
.align 4, 0x90
__Z10neg_one_fni: ## @_Z10neg_one_fni
Leh_func_begin1:
pushl %ebp
movl %esp, %ebp
cmpl $0, 8(%ebp)
jns LBB1_2 ## %if.else.i
popl %ebp
jmp __Z3foov ## TAILCALL
LBB1_2: ## %if.else.i
popl %ebp
jmp __Z3barv ## TAILCALL
Leh_func_end1:
Таким образом, вы можете видеть, что компилятор встроил шаблон, а затем оптимизировал ветку, когда мог. Таким образом, вид преобразования, на который вы надеетесь, происходит в современных компиляторах. Я получил аналогичные результаты (но менее понятную сборку) и от старого компилятора g ++ 4.0.1.
Я решил, что этот пример не был достаточно похож на ваш первоначальный случай (так как он не включал троичный оператор), поэтому я изменил его на следующее: (Получение результатов такого же рода)
template<int X>
void do_something_else( int _ncx ) {
static const int _cx = (X<0) ? (-X) : (X);
if ( (X < 0) ? (_cx > 5) : (_ncx > 5)) {
foo();
} else {
bar();
}
}
void a(int arg) {
do_something_else<1>(arg);
}
void b(int arg) {
do_something_else<-1>(arg);
}
Это создает сборку
Это все еще содержит ветку.
__Z1ai: ## @_Z1ai
Leh_func_begin2:
pushl %ebp
movl %esp, %ebp
cmpl $6, 8(%ebp)
jl LBB2_2 ## %if.then.i
popl %ebp
jmp __Z3foov ## TAILCALL
LBB2_2: ## %if.else.i
popl %ebp
jmp __Z3barv ## TAILCALL
Leh_func_end2:
Ветка оптимизирована подальше.
__Z1bi: ## @_Z1bi
Leh_func_begin3:
pushl %ebp
movl %esp, %ebp
popl %ebp
jmp __Z3barv ## TAILCALL
Leh_func_end3:
Переместите условное за пределы цикла:
...
if ((X < 0) ? (_cx > 5) : (_ncx > 5)) {
for (unsigned int i = 0; i < 1000000; ++i) {
/* SOMETHING */
}
} else {
for (unsigned int i = 0; i < 1000000; ++i) {
/* SOMETHING */
}
}
...
Таким образом, вы не зависите от оптимизации компилятора для удаления неиспользуемого кода; если неиспользуемая часть условия не удаляется компилятором, вы просто платите за условную ветвь один раз, а не каждый раз за цикл.
Это, вероятно, зависит от того, насколько умен ваш компилятор. Я рекомендую вам написать небольшую тестовую программу, чтобы самостоятельно проверить ее в своей среде, чтобы убедиться в этом.