Почему атрибут gnu_inline так сильно влияет на генерацию кода по сравнению с обычным встраиванием? - PullRequest
1 голос
/ 27 апреля 2019

Почему использование extern inline __attribute__((gnu_inline)) сверх static inline так сильно влияет на генерацию кода GCC 8.3?

Пример кода основан на коде glibc bsearch (сборка с -O3):

#include <stddef.h>

extern inline __attribute__((gnu_inline))
void *bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
   int (*__compar)(const void *, const void *))
{
    size_t __l, __u, __idx;
    const void *__p;
    int __comparison;

    __l = 0;
    __u = __nmemb;
    while (__l < __u) {
        __idx = (__l + __u) / 2;
        __p = (void *) (((const char *) __base) + (__idx * __size));
        __comparison = (*__compar) (__key, __p);
        if (__comparison < 0)
            __u = __idx;
        else if (__comparison > 0)
            __l = __idx + 1;
        else
            return (void *) __p;
    }

  return NULL;
}

static int comp_int(const void *a, const void *b)
{
    int l = *(const int *) a, r = *(const int *) b;
    if (l > r) return 1;
    else if (l < r) return -1;
    else return 0;
}

int *bsearch_int(int key, const int *data, size_t num)
{
    return bsearch(&key, data, num, sizeof(int), &comp_int);
}

Код, сгенерированный для функции bsearch_int:

bsearch_int:
        test    rdx, rdx
        je      .L6
        xor     r8d, r8d
.L5:
        lea     rcx, [rdx+r8]
        shr     rcx
        lea     rax, [rsi+rcx*4]
        cmp     DWORD PTR [rax], edi
        jl      .L3
        jg      .L10
        ret
.L10:
        mov     rdx, rcx
.L4:
        cmp     rdx, r8
        ja      .L5
.L6:
        xor     eax, eax
        ret
.L3:
        lea     r8, [rcx+1]
        jmp     .L4

Если я использую static inline над extern inline __attribute__((gnu_inline)), я получу гораздо больший код:

bsearch_int:
        xor     r8d, r8d
        test    rdx, rdx
        je      .L11
.L2:
        lea     rcx, [r8+rdx]
        shr     rcx
        lea     rax, [rsi+rcx*4]
        cmp     edi, DWORD PTR [rax]
        jg      .L7
        jl      .L17
.L1:
        ret
.L17:
        cmp     r8, rcx
        jnb     .L11
        lea     rdx, [r8+rcx]
        shr     rdx
        lea     rax, [rsi+rdx*4]
        cmp     edi, DWORD PTR [rax]
        jg      .L12
        jge     .L1
        cmp     r8, rdx
        jnb     .L11
.L6:
        lea     rcx, [r8+rdx]
        shr     rcx
        lea     rax, [rsi+rcx*4]
        cmp     DWORD PTR [rax], edi
        jl      .L7
        jle     .L1
        mov     rdx, rcx
        cmp     r8, rdx
        jb      .L6
.L11:
        xor     eax, eax
.L18:
        ret
.L12:
        mov     rax, rcx
        mov     rcx, rdx
        mov     rdx, rax
.L7:
        lea     r8, [rcx+1]
        cmp     r8, rdx
        jb      .L2
        xor     eax, eax
        jmp     .L18

Что заставляет GCC генерировать намного более короткий код в первом случае?

Примечания:

  • Кажется, что на Clang это не влияет.

Ответы [ 2 ]

1 голос
/ 27 апреля 2019

Ответ ниже был основан на редакции 2 вопроса , тогда как редакция 3 изменила, основываясь на этом ответе, значение вопроса, после чего большая часть ответа ниже может показаться немного излишней. контекст. Оставив этот ответ в том виде, в котором он был написан, основываясь на редакции 2.


С 6.31.1 Атрибуты общих функций руководства GCC [ выделение шахта]:

gnu_inline

Этот атрибут должен использоваться с функцией, которая также объявлена с ключевым словом inline. Он направляет GCC обрабатывать функцию как если он был определен в режиме gnu90 даже при компиляции в C99 или gnu99 режим.

...

И, с Раздел 6.42 Встроенная функция работает так же быстро, как макрос [ выделение мое]:

Когда функция является одновременно inline и static, если все вызовы функция интегрирована в вызывающую , а адрес функции никогда не используется, то собственный код ассемблера функции никогда не будет ссылки. В этом случае GCC фактически не выводит ассемблер код для функции , если не указан параметр -fkeep-инлайн-функции.

...

Остальная часть этого раздела относится к вставке GNU C90 .

Если функция inline не static, компилятор должен предположим, что могут быть звонки из других исходных файлов; так как глобальный Символ может быть определен только один раз в любой программе, функция не должна быть определено в других исходных файлах, поэтому вызовы в них не могут быть интегрированный. Следовательно, не- static inline функция всегда составлено самостоятельно обычным способом .

Если вы укажете и inline, и extern в функции определение , тогда определение используется только для встраивания . В нет case - это функция, скомпилированная самостоятельно, даже если вы ссылаетесь на ее адрес явно. Такой адрес становится внешней ссылкой, так как если вы только объявили функцию и не определили ее.

...

Здесь указывается, что атрибут gnu_inline будет влиять только на следующие два случая, где будет применяться вставка GNU C90:

  • с использованием extern и inline и
  • только с использованием inline.

Как и ожидалось, мы видим большую разницу в сгенерированной сборке между этими двумя.

Однако при использовании static и inline правила вставки в GNU C90 не применяются (или, скорее, не относятся конкретно к этому случаю), что означает, что атрибут gnu_inline не имеет значения.

Действительно, эти две подписи приводят к одной и той же сборке:

static inline __attribute__((gnu_inline))
void *bsearch ...

static inline
void *bsearch ...

Поскольку extern inline и static inline используют два разных подхода к встраиванию (стратегия встраивания GNU C90 и более современные стратегии встраивания, соответственно), можно ожидать, что сгенерированная сборка может немного отличаться между этими двумя. Тем не менее, оба они дают существенно меньшую производительность сборки, чем при использовании только inline (в этом случае, как указано выше, функция всегда компилируется самостоятельно).

0 голосов
/ 27 апреля 2019

Компилируется только потому, что вы не используете оптимизацию, а встраивание не активно. Попробуйте, например, с -O1, и ваш код вообще не будет компилироваться.

Код отличается, потому что при использовании static компилятору не нужно заботиться о соглашениях о вызовах, так как функция не будет видна другим модулям компиляции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...