g++ hello.cc get_name.cc ‑o hello
typedef struct {
uint8_t h;
uint8_t m;
} hh_mm;
bool
first_is_gt(hh_mm a, hh_mm b) {
if (a.h > b.h)
return true;
if (a.h == b.h)
return a.m > b.m;
return false;
}
Если в программе не выявлено ошибок, начинается процесс её перевода в промежуточное
представление, которое:
На примере языка C++:
class my_class {
public:
my_class(int a, int b) : m_a(a), m_b(b) { }
int get_sum() { return m_a + m_b; }
private:
int m_a, m_b;
};
struct my_class {
int m_a, m_b;
};
void _ZN8my_classC2Eii(struct my_class *this, int a, int b) {
this->m_a = a;
this->m_b = b;
}
int _ZN8my_class7get_sumEv(struct my_class *this) {
return this->m_a + this->m_b;
}
void copy(char *dest, const char *src) {
while (*dest++ = *src++)
;
}
void copy(char *dest, const char *src) {
char *dest_tmp, *src_tmp;
do {
dest_tmp = dest;
dest = dest_tmp + 1;
src_tmp = src;
src = src_tmp + 1;
char val = *src;
*dest_tmp = val;
} while (val != 0);
}
void foo(int a, int b) {
if (a > b) {
int x = a, y = b;
bar(x, y);
} else {
int a = 1, y = 2;
baz(a, y);
}
}
void foo(int a, int b) {
int x, y_1, y_2, a_2;
if (a > b) {
x = a; y_1 = b;
bar(x, y_1);
} else {
a_2 = 1; y_2 = 2;
baz(a, y_2);
}
}
void cmp(int a, int b)
{
if (a > b) {
foo(1);
} else {
if (a == b)
foo(0);
else
foo(-1);
}
}
void cmp(int a, int b) {
bb_0:
if (a > b) goto bb_1; else goto bb_2;
bb_1:
foo(1);
goto bb_5;
bb_2:
if (a == b) goto bb_3; else goto bb_4;
bb_3:
foo(0);
goto bb_5;
bb_4:
foo(-1);
bb_5:
return;
}
void count(int n) {
for (int i = 0; i < n; i += 5)
foo(i);
}
void count(int n) {
int i;
bb_0:
i = 0;
goto bb_2;
bb_1:
foo(i);
i = i + 5;
bb_2:
if (i < n) goto bb_1; else goto bb_3;
bb_3:
return;
}
int redundant(int x) {
int y = 10;
y += x;
if (x > 0)
y = 1;
else
y = 2;
return y;
}
int redundant(int x) {
int y;
bb_0:
y = 10;
y = y + x;
if (x > 0) goto bb_1; else goto bb_2;
bb_1:
y = 1;
goto bb_2;
bb_2:
y = 2;
bb_3:
return y;
}
int redundant(int x) {
int y_0, y_1, y_2, y_3, y_4;
bb_0:
y_0 = 10;
y_1 = y_0 + x;
if (x > 0) goto bb_1; else goto bb_2;
bb_1:
y_2 = 1;
goto bb_3;
bb_2:
y_3 = 2;
bb_3:
y_4 = φ(y_2, y_3);
return y4;
}
Оптимизация, выполняемая компилятором — улучшение (в отличие от понятия оптимизации в математике).
Изначальные источники не оптимальности:По охватываемой области:
Отдельно можно выделить машинно-зависимые оптимизации, оптимизации циклов и др.
/* ~~x -> x */ (simplify (bit_not (bit_not @0)) @0) /* Simplify sin(x) / tan(x) -> cos(x). */ (simplify (rdiv (SIN:s @0) (TAN:s @0)) (if (! HONOR_NANS (@0) && ! HONOR_INFINITIES (@0)) (COS @0)))
Затрагивают сразу несколько базовых блоков (вплоть до функции целиком).
int encode_image(int coeff, image_data *in, output_stream *out) {
if (coeff < 0 || coeff > 100)
return -EINVAL;
return do_encode(coeff, in, out);
}
int encode_image_simple(quality q, image_data *in, output_stream *out) {
int coeff;
switch (q) {
case Q_LOW:
coeff = 50; break;
case Q_MED:
coeff = 75; break;
case Q_HIGH:
coeff = 90; break;
default:
return -EINVAL;
}
return encode_image(coeff, in, out);
}
int encode_image_simple(quality q,
image_data *in,
output_stream *out) {
int coeff;
switch (q) {
case Q_LOW:
coeff = 50; break;
case Q_MED:
coeff = 75; break;
case Q_HIGH:
coeff = 90; break;
default:
return -EINVAL;
}
// coeff: [50, 90]
if (coeff < 0 || coeff > 100)
return -EINVAL;
return do_encode(coeff, in, out);
}
int encode_image_simple(quality q,
image_data *in,
output_stream *out) {
int coeff;
switch (q) {
case Q_LOW:
coeff = 50; break;
case Q_MED:
coeff = 75; break;
case Q_HIGH:
coeff = 90; break;
default:
return -EINVAL;
}
return do_encode(coeff, in, out);
}
Состоит из несколько фаз (проходов). Анализ циклов включает в себя:
Полученные данные позволяют выполнять преобразования циклов, которые:
void unswitch(char *x, int n, bool flag)
{
if (flag) {
for (int i = 0; i < n; i++)
if (flag)
foo(x[i]);
else
bar(x[i]);
} else {
for (int i = 0; i < n; i++)
if (flag)
foo(x[i]);
else
bar(x[i]);
}
}
void unswitch(char *x, int n, bool flag)
{
if (flag) {
for (int i = 0; i < n; i++)
if (true)
foo(x[i]);
else
bar(x[i]);
} else {
for (int i = 0; i < n; i++)
if (false)
foo(x[i]);
else
bar(x[i]);
}
}
void unswitch(char *x, int n,
bool flag)
{
for (int i = 0; i < n; i++)
if (flag)
foo(x[i]);
else
bar(x[i]);
}
void unswitch(char *x, int n,
bool flag)
{
if (flag) {
for (int i = 0; i < n; i++)
foo(x[i]);
} else {
for (int i = 0; i < n; i++)
bar(x[i]);
}
}
Распространяют по графу вызовов информацию:
Собранная информация позволяет оптимизировать функцию либо создать несколько версий одной функции, которые лучше поддаются оптимизации.
// test.c
struct point {
int x, y, z;
};
static int
foo(const struct point *p) {
return p->x + p->y;
}
int
bar(int x, int y) {
struct point p = {x, y, 0};
return foo(&p);
}
// После применения оптимизации
// "межпроцедурная скалярная
// замена агрегатов"
static int
foo_isra_0(int p0, int p1) {
return p0 + p1;
}
int
bar(int x, int y) {
return foo_isra_0(x, y);
}
static long add2(long *a, long *b) {
return *a + *b;
}
long add3(long *a, long *b, long *c) {
return add2(a, b) + *c;
}
; gcc -c test.c -O -fno-inline
add2: ; rsi = a, rdi = b
mov rax, QWORD PTR [rsi]
add rax, QWORD PTR [rdi]
ret
add3: ; rsi=a, rdi=b, rdx=c
push rbx
mov rbx, rdx
call add2
add rax, QWORD PTR [rbx]
pop rbx
ret
; gcc -c test.c -O
add3: ; rsi=a, rdi=b, rdx=c
mov rax, QWORD PTR [rsi]
add rax, QWORD PTR [rdi]
add rax, QWORD PTR [rdx]
ret
#include <vector>
#include <numeric>
long sum(const std::vector<long>& vec) {
return std::accumulate(vec.begin(), vec.end(), 0L);
}
Встраивание производится двумя проходами оптимизатора, которые используют разные алгоритмы.
Жаркая дискуссия Линуса Торвальдса и мэйнтейнера GCC на тему эвристик
Попробуем скомпилировать программу:
uint8_t mod(uint8_t a, uint8_t b) {
return a % b;
}
Описание архитектуры процессора содержит таблицы поддерживаемых стандартных операций. Компилятор попытается найти в них что-нибудь подходящее, например:
В итоге (на x86) удастся найти шаблон udivmod для uint8_t
// Псевдокод шаблона udivmod
void udivmodqi4(uint8_t dividend, uint8_t divisor,
uint8_t *remainder, uint8_t* quotient)
{
*quotient = dividend / divisor;
*remainder = dividend % divisor;
}
// Но в x86 инструкция деления работает несколько иначе.
// Псевдокод шаблона инструкции div для 8-битного делителя
// (с некоторыми допущениями):
void udivmodhiqi3(uint16_t dividend, uint8_t divisor, uint16_t *result)
{
uint16_t quotient = (uint8_t)(dividend / (uint16_t)divisor);
uint16_t remainder = (uint8_t)(dividend % (uint16_t)divisor);
*result = (remainder << 8) | quotient;
}
// Реальная инструкция процессора делает не совсем то, что нам нужно
void udivmodhiqi3(uint16_t dividend, uint8_t divisor, uint16_t *result)
{
uint16_t quotient = (uint8_t)(dividend / (uint16_t)divisor);
uint16_t remainder = (uint8_t)(dividend % (uint16_t)divisor);
*result = (remainder << 8) | quotient;
}
// Напишем адаптер
void udivmodqi4(uint8_t dividend, uint8_t divisor,
uint8_t *remainder, uint8_t* quotient)
{
union {
uint16_t u16;
struct { uint8_t low; uint8_t high; };
} result;
udivmodhiqi3((uint16_t)dividend, divisor, &result.u16);
*remainder = result.high;
*quotient = result.low;
}
;; Операнд 0 - результат ((remainder << 8) | quotient) ;; Операнд 1 - делимое ;; Операнд 2 - делитель (define_insn "udivmodhiqi3" [(set (match_operand:HI 0 "register_operand" "=a") (ior:HI (ashift:HI (zero_extend:HI (truncate:QI (mod:HI (match_operand:HI 1 "register_operand" "0") (zero_extend:HI (match_operand:QI 2 "nonimmediate_operand" "qm"))))) (const_int 8)) (zero_extend:HI (truncate:QI (div:HI (match_dup 1) (zero_extend:HI (match_dup 2))))))) (clobber (reg:CC FLAGS_REG))] "TARGET_QIMODE_MATH" "div{b}\t%2" [(set_attr "type" "idiv") (set_attr "mode" "QI")])
#define likely(x) __builtin_expect((x),1)
int foo(int x) {
if (likely(x < 0))
x = bar(x);
else
x = baz(x);
return x + 5;
}
foo: ; edi = x
sub rsp, 8
test edi, edi
jns .L2
call bar
.L3:
add eax, 5
add rsp, 8
ret
.L2:
call baz
jmp .L3
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong place and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.Donald Ervin Knuth
Agner Fog Optimizing software in C++
Поддержкой back-end компонентов обычно занимаются разработчики железа:
Языки Go и Ada также поддерживаются крупными компаниями:
Попробуйте использовать для сборки вашего кода еженедельные снэпшоты GCC (и прогонять unit-тесты).
Взамен за потраченные усилия вы: