Непоследовательное поведение gcc для __attribute ((const))

Я столкнулся с очень странным поведением в 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, но это не помогает: все целые, кажется, предварительно вычисляются во время компиляции.

6

Решение

Компилятор вам не доверяет.
Поскольку у вас есть ссылочный аргумент, компилятор, похоже, не доверяет вашему 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
5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]