Почему сгенерированные переключателем переходы gcc быстрее, чем эквивалентные вызовы функций, но только при статическом связывании? - PullRequest
0 голосов
/ 28 декабря 2018

Мне было любопытно, как выполняются переходы переключения по отношению к вызовам функций, поэтому я сделал быстрый тест:

#!/bin/bash -eu
cat > get.c <<EOF
#include <stdint.h>
int get(int (Getter)(void))
{
    uintptr_t getter=(uintptr_t)Getter; 
    if(1){
        switch(getter){
        case 0: return $RANDOM;
        case 1: return $RANDOM;
        case 2: return $RANDOM;
        case 3: return $RANDOM;
        case 4: return $RANDOM;
        case 5: return $RANDOM;
        default: return Getter();
        }
    }else{
        if(0==getter) return $RANDOM;
        else if(1==getter) return $RANDOM;
        else if(2==getter) return $RANDOM;
        else if(3==getter) return $RANDOM;
        else if(4==getter) return $RANDOM;
        else if(5==getter) return $RANDOM;
        else return Getter();
    }
}
EOF
cat > main.c <<EOF
int get(int (Getter)(void));
int Getter(void){ return 42; }
int main(int C, char**V)
{
    if(C==1)
        for(int i=0; i<1000000000;i++)
            get((int(*)(void))4);
    else
        for(int i=0; i<1000000000;i++)
            get(Getter);

}
EOF
: ${CC:=gcc}
arg='-Os -fpic'
for c in *.c; do $CC $arg -c $c; done
$CC get.o -o libget.so -shared
$CC main.o $PWD/libget.so -o dso
$CC main.o get.o -o dso -o static
set -x
time ./dso
time ./dso 1
time ./static
time ./static 1

Время (относительно стабильное):

+ ./dso

real    0m3.778s
user    0m3.709s
sys 0m0.056s
+ ./dso 1

real    0m3.739s
user    0m3.736s
sys 0m0.000s
+ ./static

real    0m2.478s
user    0m2.477s
sys 0m0.000s
+ ./static 1

real    0m3.425s
user    0m3.411s
sys 0m0.000s

Почемупрыжки переключателя работают немного лучше, но только когда функция связана статически?

Разборка различий (генерируемых sdiff) динамической и статической версий соответственно:

000000000000111a <get>:                       | 0000000000001180 <get>:
    cmp    $0xc,%rdi                        cmp    $0xc,%rdi
    ja     1178 <get+0x5e>                    |     ja     11de <get+0x5e>
    lea    0xed9(%rip),%rdx        # 2000 <_fini+0xe80>   |     lea    0xe77(%rip),%rdx        # 2004 <_IO_stdin_used
    movslq (%rdx,%rdi,4),%rax                   movslq (%rdx,%rdi,4),%rax
    add    %rdx,%rax                        add    %rdx,%rax
    jmpq   *%rax                            jmpq   *%rax
    mov    $0x132b,%eax                     mov    $0x132b,%eax
    retq                                retq   
    mov    $0x2740,%eax                     mov    $0x2740,%eax
    retq                                retq   
    mov    $0x79b6,%eax                     mov    $0x79b6,%eax
    retq                                retq   
    mov    $0x5234,%eax                     mov    $0x5234,%eax
    retq                                retq   
    mov    $0x6389,%eax                     mov    $0x6389,%eax
    retq                                retq   
    mov    $0x37de,%eax                     mov    $0x37de,%eax
    retq                                retq   
    mov    $0x6a22,%eax                     mov    $0x6a22,%eax
    retq                                retq   
    mov    $0x1a35,%eax                     mov    $0x1a35,%eax
    retq                                retq   
    mov    $0x2ce8,%eax                     mov    $0x2ce8,%eax
    retq                                retq   
    mov    $0x4fed,%eax                     mov    $0x4fed,%eax
    retq                                retq   
    mov    $0xfe3,%eax                      mov    $0xfe3,%eax
    retq                                retq   
    mov    $0x4229,%eax                     mov    $0x4229,%eax
    retq                                retq   
    jmpq   *%rdi                            jmpq   *%rdi
    mov    $0x529e,%eax                     mov    $0x529e,%eax
    retq                                retq   
                                  <

1 Ответ

0 голосов
/ 28 декабря 2018

Вызовы не могут быть встроенными (потому что вы поместили определение в отдельный файл и не использовали оптимизацию во время соединения).

Я думаю, что вы измеряете дополнительные издержки вызовачерез PLT при вызове функции в разделяемой библиотеке, в традиционном стиле Unix, который gcc делает по умолчанию. Используйте -fno-plt, чтобы выдавать инструкции вызова с косвенной памятью, которые напрямую используют запись GOT, вместо call, используякосвенная память jmp.См. Извините состояние динамических библиотек в Linux для получения дополнительной информации о накладных расходах PLT или разберите его самостоятельно.(TODO: добавить разбор к этому ответу.)

Я ожидаю, что -fno-plt сделает обе версии почти одинаковыми.


asm для обеих версий "get "идентичен, по модулю разные случайные числа и разные адреса . Они предположительно выполняют одинаково, оба медленные, потому что gcc пропускает оптимизацию превращения switch в поиск таблицы. См. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85585 для этого и связанных с ним вещей. (Кстати, gcc сжимает таблицу в смещения вместо использования классической таблицы переходов необработанных указателей, потому что она пытается избежать абсолютных адресов везде, даже в виде данных. Некоторые цели не поддерживают исправления дажедля этого, и gcc в настоящее время избегает их даже в таких целях, как x86-64 / Linux, где было бы неплохо с исправлениями во время выполнения, но, конечно, глупо делать косвенную ветвь вместо того, чтобы просто искать данные в таблице в этом случае.)

Также связано: 32-разрядные абсолютные адреса больше не разрешены в x86-64 Linux? говорит о некоторыхстоимость -fpie и -fpic.В этом случае нечего сохранять, пропуская -fpic и / или используя -fno-pie -no-pie, потому что отдельные файлы также не позволяют функции вставки, а не только возможной видимости вставки символа / символа ELF.

...