Когда компилятор будет консервативен в отношении оптимизации разыменования указателей, если вообще будет?
Итак, я недавно поинтересовался, насколько хорошо работает компилятор (gcc (GCC) 4.8.3
быть тем, о котором идет речь) заключается в оптимизации указателей и указателей.
Первоначально я создал простое целое и целочисленный указатель и реализовал операции над ним, чтобы я мог распечатать его. Как и ожидалось, все операции, которые были жестко запрограммированы, были оптимизированы с помощью разыменованного указателя или нет.
call __main
leaq .LC0(%rip), %rcx
movl $1, %edx
call printf
И даже после создания функции, которая получает указатель типа int, разыменовывает его и изменяет, он все равно был полностью оптимизирован.
call __main
leaq .LC0(%rip), %rcx
movl $-1, %edx
call printf
Теперь, когда я обработал мой указатель как пустоту и внес изменения, приведя его к типу char и разыменовав его, он на самом деле все еще отлично optmized (дополнительный вызов mov, так как я первоначально рассматривал его как 8-байтовое значение, а затем как 1 значение байта для разыменования указателя)
call __main
movl $4, 44(%rsp)
movb $2, 44(%rsp)
leaq .LC0(%rip), %rcx
movl 44(%rsp), %eax
leal 1(%rax), %edx
call printf
Итак, на мой вопрос (ы):
Насколько последовательна оптимизация компилятора в отношении разыменования указателей? В каких случаях он предпочел бы быть консервативным?
Если бы все мои указатели в проекте были объявлены с ключевым словом restrict, могу ли я поверить, что он будет так же оптимизирован, как если бы "указатели вообще не использовались"?
(при условии, что нет volatile
случаи)
PS: Я знаю, что компилятор, как правило, выполняет достаточно хорошую работу, и что программист, беспокоящийся о помощи компилятору в незначительных оптимизациях, в общем, непродуктивен (как многие указывают в ответах на вопросы stackru на вопросы, касающиеся оптимизации). Тем не менее, у меня все еще есть любопытство по этому поводу.
Ps².: gcc -O3 -S -c main.c
была команда, используемая для генерации кода сборки
Код C: (по запросу)
1:
#include <stdio.h>
int main (void)
{
int a = 4;
int *ap = &a;
*ap = 0;
a += 1;
printf("%d\n", a);
return 0;
}
2:
#include <stdio.h>
void change(int *p) {
*p -= 2;
}
int main (void)
{
int a = 4;
int *ap = &a;
*ap = 0;
change(ap);
a += 1;
printf("%d\n", a);
return 0;
}
3:
#include <stdio.h>
void change(void *p) {
*((char*)p) += 2;
}
int main (void)
{
int a = 4;
void *ap = (void*) &a;
*((char*)(ap)) = 0;
change(ap);
a += 1;
printf("%d\n", a);
return 0;
}
1 ответ
LLVM и GCC оба испускают статический код формы с одним назначением как часть анализа оптимизации. Одним из полезных свойств кода SSA является то, что он точно показывает поток влияния для назначения - то есть он знает, какие назначения приводят к другим назначениям, и поэтому может определять, какие значения могут влиять на все другие.
Первая цепочка влияния выглядит примерно так
a1 -> константа (0) -> ap -> a2
Второе: a1 -> константа (0) -> ap -> p -> a2
Третье очень похоже на второе. (Извините, эта запись в значительной степени придумана, но я надеюсь, что она иллюстрирует мою точку зрения.)
Поскольку довольно просто доказать, что влияние a на ap является детерминированным, он может свободно разыменовывать "рано" и объединять инструкции в одно (хотя в первых двух случаях это не самое точное утверждение, так как константа перезаписывает исходную ссылку и позволяет компилятору доказать, что исходное присвоение не передается до конца кода.
Заставить компилятор быть более консервативным в отношении разыменования может потребоваться усложнение, чтобы избежать понимания компилятора (я думаю, это сложно в статической программе), или более вероятно, что компилятор вызовет функцию phi в процессе SSA (в терминах непрофессионалов), чтобы вызывать влияние на назначение нескольких предыдущих назначений) недетерминированным способом.
Ключевое слово restrict намекает компилятору, что два указателя различны. Это не будет ограничивать использование разыменования во время выполнения, если код, создавший этот указатель, все еще имеет недетерминированный источник (например, если созданные во время выполнения данные влияют на выбор того, какое значение указателя было разыменовано - я думаю, это может произойти, если сериализованный указатель был отправлен в программу из внешнего источника?)