Я столкнулся с очень странным поведением в gcc относительно операторов и функций, помеченных __attribute((const))
, Логические и арифметические операторы приводят к разной оптимизации, и я не понимаю, почему.
Это не совсем ошибка, так как __attribute((const))
это только намек, и нет никаких гарантий его эффективности, но все же это очень удивительно. У кого-нибудь есть объяснение?
Вот код Поэтому я определяю __attribute((const))
функция:
int f(int & counter) __attribute((const));
int f(int & counter) {
++counter;
return 0;
}
Затем я определяю макрос тестирования оператора. Это делается с помощью макросов, а не шаблонов / функторов для представления простого кода компилятору и упрощения оптимизации:
int global = 0; // forces results to be computed
#define TestOp(OP) \
{ \
int n = 0; \
global += (f(n) OP f(n)); \
std::cout << "op" #OP " calls f " << n << " times" << std::endl; \
}
И, наконец, я тестирую различные операторы следующим образом. Комментарии соответствуют выводу с g++-4.8 -std=c++11 -O2 -Wall -pedantic
тот же выход при -O3
а также -Ofast
int main() {
// all calls optimized away
TestOp(^) // 0
TestOp(-) // 0
// one call is optimized away
TestOp(|) // 1
TestOp(&) // 1
TestOp(||) // 1
TestOp(&&) // 1
// no optimization
TestOp(+) // 2
TestOp(*) // 2
return global;
}
Мой вопрос: почему арифметические операторы дают два вызова? Почему не мог f()+f()
быть оптимизирован как 2*f()
? Есть ли способ помочь / форсировать эту оптимизацию?
Сначала я думал, что умножение может быть дороже, но я попытался с f()+....+f()
и 10 дополнений до сих пор не сводятся к 10*f()
, Кроме того, так как это int
арифметика, порядок работы не имеет значения (в отличие от float
с).
Я также проверил asm, но это не помогает: все целые, кажется, предварительно вычисляются во время компиляции.
Компилятор вам не доверяет.
Поскольку у вас есть ссылочный аргумент, компилятор, похоже, не доверяет вашему const
атрибут — const
функция должна смотреть только на значения, передаваемые через аргументы (не ссылки или разыменование указателей).
Еще один способ проверить это, чтобы сломать const
функционировать в отдельном блоке компиляции:
test1.cpp:
#include <stdio.h>
int global = 0; // forces results to be computed
int f(int i) __attribute((const));
void print_count(void);
#define TestOp(OP) \
{ \
int n = 0; \
global += (f(n) OP f(n)); \
printf("op %s ", #OP);\
print_count();\
}
int main() {
// all calls optimized away
TestOp(^) // 0
TestOp(-) // 0
// one call is optimized away
TestOp(|) // 1
TestOp(&) // 1
TestOp(||) // 1
TestOp(&&) // 1
// no optimization
TestOp(+) // 2
TestOp(*) // 2
return global;
}
counter.cpp:
#include <stdio.h>
static int counter = 0;
int f(int i) {
++counter;
return 0;
}
void print_count(void)
{
printf("counter %d\n", counter);
counter = 0;
}
Теперь компилятор выясняет, что нет необходимости вызывать f(0)
до тех пор f(0) | f(0)
и результат этого одного звонка f(0)
повторно используется для других случаев.
$ g++ -O2 -c counter.cpp && g++ -O2 -c test.cpp && g++ counter.o test.o && ./a.out
op ^ counter 0
op - counter 0
op | counter 1
op & counter 0
op || counter 0
op && counter 0
op + counter 0
op * counter 0
Других решений пока нет …