Могут ли компиляторы C оптимизировать адреса в встроенных функциях? - PullRequest
2 голосов
/ 05 апреля 2011

Допустим, у меня есть следующий код:

int f() {
  int foo = 0;
  int bar = 0;

  foo++;
  bar++;

  // many more repeated operations in actual code
  foo++;
  bar++;

  return foo+bar;
}

Абстрагируя повторяющийся код в отдельные функции, получаем

static void change_locals(int *foo_p, int *bar_p) {
  *foo_p++;
  *bar_p++;
}

int f() {
  int foo = 0;
  int bar = 0;

  change_locals(&foo, &bar);
  change_locals(&foo, &bar);

  return foo+bar;
}

Я ожидаю, что компилятор встроит функцию change_locals и оптимизирует такие вещи, как *(&foo)++ в полученном коде, до foo++.

Если я правильно помню, получение адреса локальной переменной обычно предотвращает некоторую оптимизацию (например, она не может быть сохранена в регистрах), но применима ли это, когда по адресу не выполняется арифметика указателей и она не выходит из функция? С большим change_locals, будет ли это иметь значение, если его объявят inline (__inline в MSVC)?

Меня особенно интересует поведение компиляторов GCC и MSVC.

Ответы [ 3 ]

3 голосов
/ 05 апреля 2011

inline (и все его кузены _inline, __inline ...) игнорируются gcc. Он может включать все, что решит, является преимуществом, за исключением более низких уровней оптимизации.

Процедура кода gcc -O3 для x86:

        .text
        .p2align 4,,15
.globl f
        .type   f, @function
f:
        pushl   %ebp
        xorl    %eax, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret
        .ident  "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"

Возвращает ноль, потому что * ptr ++ не делает то, что вы думаете. Исправление приращений до:

    (*foo_p)++;
    (*bar_p)++;

результат в

        .text
        .p2align 4,,15
.globl f
        .type   f, @function
f:
        pushl   %ebp
        movl    $4, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret

Так что он напрямую возвращает 4. Он не только встроил их, но и оптимизировал расчеты.

Vc ++ по сравнению с 2005 предоставляет аналогичный код, но он также создал недоступный код для change_locals(). Я использовал командную строку

/O2 /FD /EHsc /MD /FA /c /TP
2 голосов
/ 05 апреля 2011

Если я правильно помню, получение адреса локальной переменной обычно предотвращает некоторые оптимизации (например, она не может быть сохранена в регистрах), но это применимо, когда по адресу не выполняется арифметика указателей, и это не 't выйти из функции?

Общий ответ таков: если компилятор может гарантировать, что никто не изменит значение за его спиной, его можно безопасно поместить в регистр.

Думайте об этом, как будто компилятор сначала выполняет встраивание, а затем преобразует все эти *&foo (что является результатом встраивания) в просто foo, прежде чем решить, следует ли их помещать в регистры в памяти в стеке.

При большем значении change_locals, будет ли это иметь значение, если он будет объявлен встроенным (__inline в MSVC)?

Опять же, вообще говоря, решает ли компилятор встроить или нетчто-то делается с помощью эвристики.Если вы явно укажете, что хотите, чтобы что-то было встроенным, компилятор, вероятно, включит это в процесс принятия решения.

1 голос
/ 05 апреля 2011

Я протестировал gcc 4.5, MSC и IntelC, используя это:

#include <stdio.h>

void change_locals(int *foo_p, int *bar_p) {
  (*foo_p)++;
  (*bar_p)++;
}

int main() {
  int foo = printf("");
  int bar = printf("");

  change_locals(&foo, &bar);
  change_locals(&foo, &bar);

  printf( "%i\n", foo+bar );
}

И все они встроили / оптимизировали значение foo + bar, но также сделали сгенерировать код для change_locals () (но не использовал его).

К сожалению, до сих пор нет гарантии, что они сделают то же самое для любой вид такой «локальной функции».

НКА:

__Z13change_localsPiS_:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %edx
    movl    12(%ebp), %eax
    incl    (%edx)
    incl    (%eax)
    leave
    ret

_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    pushl   %ebx
    subl    $28, %esp
    call    ___main
    movl    $LC0, (%esp)
    call    _printf
    movl    %eax, %ebx
    movl    $LC0, (%esp)
    call    _printf
    leal    4(%ebx,%eax), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    xorl    %eax, %eax
    addl    $28, %esp
    popl    %ebx
    leave
    ret
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...