преамбула
Я использую avr-g ++ для программирования микроконтроллеров AVR, и поэтому мне всегда нужно получать очень эффективный код.
GCC обычно может оптимизировать функцию, если ее аргументом являются константы времени компиляции, например, У меня есть функция pin_write(uint8_t pin, bool val)
которые определяют регистры AVR для pin
(используя мою специальную карту из целого числа pin
в пару порт / пин) и записать в эти регистры соответствующие значения. Эта функция не слишком мала из-за своей общности. Но если я вызову эту функцию с постоянной времени компиляции pin
а также val
GCC может выполнять все вычисления во время компиляции и исключать этот вызов для пары инструкций AVR, например,
sbi PORTB,1
sbi DDRB,1
иноходь
Давайте напишем такой код:
class A {
int x;
public:
A(int x_): x(x_) {}
void foo() { pin_write(x, 1); }
};
A a(8);
int main() {
a.foo();
}
У нас есть только один объект класса A, и он инициализируется константой (8). Таким образом, можно сделать все вычисления во время компиляции:
foo() -> pin_write(x,1) -> pin_write(8,1) -> a couple of asm instructions
Но GCC не делает этого.
Удивительно, но если я удалю глобальный A a(8)
и пиши просто
A(8).foo()
Я получаю именно то, что хочу:
00000022 <main>:
22: c0 9a sbi 0x18, 0 ; 24
24: b8 9a sbi 0x17, 0 ; 23
Вопрос
Итак, есть ли способ заставить GCC производить все возможные вычисления во время компиляции для отдельных глобальных объектов с постоянными инициализаторами?
Из-за этой проблемы мне приходится вручную расширять такие случаи и заменять мой оригинальный код следующим:
const int x = 8;
class A {
public:
A() {}
void foo() { pin_write(x, 1); }
}
UPD. Это очень замечательно A(8).foo()
внутри main
оптимизированы до 2 ассемблерных инструкций. A a(8); a.foo()
тоже! Но если я объявлю A a(8)
так как глобальный компилятор выдает большой общий код. Я пытался добавить static
— это не помогло. Зачем?
Но если я объявлю
A a(8)
так как глобальный компилятор выдает большой общий код. Я пытался добавитьstatic
— это не помогло. Зачем?
По моему опыту, gcc очень неохотно, если объект / функция имеет внешнюю связь. Поскольку у нас нет вашего кода для компиляции, я сделал слегка измененную версию вашего кода:
#include <cstdio>
class A {
int x;
public:
A(int x_): x(x_) {}
int f() { return x*x; }
};
A a(8);
int main() {
printf("%d", a.f());
}
Я нашел 2 способа добиться того, чтобы сгенерированная сборка соответствовала этому:
int main() {
printf("%d", 64);
}
На словах: устранить все во время компиляции, чтобы остался только необходимый минимум.
Один из способов добиться этого с помощью clang и gcc:
#include <cstdio>
class A {
int x;
public:
constexpr A(int x_): x(x_) {}
constexpr int f() const { return x*x; }
};
constexpr A a(8);
int main() {
printf("%d", a.f());
}
gcc 4.7.2 уже все устраняет при -O1
, лязг 3,5 багажника нужен -O2
,
Еще один способ добиться этого:
#include <cstdio>
class A {
int x;
public:
A(int x_): x(x_) {}
int f() const { return x*x; }
};
static const A a(8);
int main() {
printf("%d", a.f());
}
Работает только с Clang на -O3
, Видимо постоянное складывание в gcc это не так агрессивно. (Как показывает clang, это можно сделать, но gcc 4.7.2 не реализовал это.)
Вы можете заставить компилятор полностью оптимизировать функцию со всеми известными константами, изменив функцию pin_write в шаблон. Я не знаю, гарантируется ли конкретное поведение стандартом.
template< int a, int b >
void pin_write() { some_instructions; }
Это, вероятно, потребует исправления всех строк, где используется pin_write.
Кроме того, вы можете объявить функцию как встроенную. Компилятору не гарантируется встроенная функция (ключевое слово inline — просто подсказка), но если это так, у него больше шансов оптимизировать константы времени компиляции (при условии, что компилятор может знать, что это константа времени компиляции, что может быть не всегда так).
Ваш a
имеет внешнюю связь, поэтому компилятор не может быть уверен, что где-то другой код его модифицирует.
Если бы вы должны были объявить a
const тогда вы даете понять, что это не должно измениться, а также перестаете иметь внешнюю связь; оба из них должны помочь компилятору быть менее пессимистичным.
(Я бы наверное объявил x
const тоже — здесь это может не помочь, но если ничего другого, то это ясно покажет компилятору и следующему читателю кода, что вы никогда его не измените.)