optymalizacja kodu wynikowego a biblioteki dzielone

Paweł Sikora pluto w agmk.net
Sob, 1 Lip 2006, 15:04:10 CEST


On Saturday 01 July 2006 00:53, Miłosz Rzeźnikowski wrote:
> mam takie szybkie pytanie:
>
> kiedys cos mi sie obilo o ucho (np. plik INSTALL z bzip2) że jeśli
> tworzymy biblioteki dzielone to kompilator nie ma możliwości użycia
> pewnych instrukcji(chyba %eax czy coś tam, nie znam sie) które to
> właśnie mają wpływ na szybkość kodu wynikowego. czy to prawda?

nie instrukcji, tylko rejestrów, które są używane przy dostępnie
do danych w relokowalnych binariach.

weźmy za przykład taki oto kod:

int x;
static int y;
int compute(void) { return x + y; }

po kompilacji (dla ix86) z -fomit-frame-pointer dla zwykłej
wykonywalnej binarki otrzymamy z grubsza coś na kształt:

compute:
        movl    y, %eax
        addl    x, %eax
        ret
(...)
        .local  y
        .comm   y,4,4

        .comm   x,4,4

szybko, ładnie i przyjemnie :)

teraz skompilujmy ten program jako relokowalny
(opcja -fPIE dla programów, lub -fPIC dla bibliotek).

compute:
        call    __i686.get_pc_thunk.cx                [1]
        addl    $_GLOBAL_OFFSET_TABLE_, %ecx          [1]
        movl    x w GOT(%ecx), %edx                  [2]
        movl    y w GOTOFF(%ecx), %eax                  [3]
        addl    (%edx), %eax                       [2]
        ret

        .local  y
        .comm   y,4,4
        .comm   x,4,4

__i686.get_pc_thunk.cx:
        movl    (%esp), %ecx
        ret

tutaj sprawa jest nieco bardziej zakręcona :)
bez zbędnego zaćiemniania [1] wylicza nam wartość
czegoś na kształt "hej, gdzie jest mój worek z danymi".

[2] to dostęp do globalnej zmiennej "x", gdzie potrzebne
są aż dwie dereferncje: x w GOT(%ecx) -> %edx  i (%edx)

[3] to dostęp do zmiennej "y" dostępnej *tylko* lokalnie
w obrębie jednostki kompilacji. tu mamy tylko jedną
dereferncję.

do całości trzeba doliczyć relokację dla zmiennej globalnej "x"

$ objdump -R got
(...)
0804953c R_386_GLOB_DAT    x

jak widzisz, dla ia32 relokowalny kod, to wrzód na dupie.
tracimy jeden rejestr (z tych całych 6 przydatnych),
dochodzą nam dodatkowe wywołania funkcji i relokacje.
finalnie kod puchnie, wolniej startuje aplikacja (bo linker
ma więcej pracy) i wolniej działa (narzut na dostęp do danych).
to tak z grubsza są wady kod relokowalnego. o zaletach tu nie
będę pisał, bo to insza bajka.

dla różnych architektur relokowalny kod wprowadza różne
spowolnienia, ale ia32 wypada tu najgorzej.

np. w przypadku x86-64 (które ma nowy tryb adresowania względem
wskaźnika instrukcji %rip) powyższe dwa warianty kodu wyglądają tak:

(bez -fPIE)
compute:
        movl    y(%rip), %eax
        addl    x(%rip), %eax
        ret

(z -fPIE)
compute:
        movq    x w GOTPCREL(%rip), %rdx
        movl    y(%rip), %eax
        addl    (%rdx), %eax
        ret

noooom wypas:)
poza tym x86-64 ma więcej rejestrów niż ia32 dzięki czemu
kod jest szyszby (np. mniej przemiatania rejestry<->pamięć).
dodatkowo np. dla sparc-a są różne warianty "relokowalności"
patrz `info gcc` i różnica między -fpic i -fPIC.

ufff,
jak czujesz niedosyt, to ponękaj gugla: ELF + GOT, ELF + PLT
oraz zerknij w http://www.phrack.org/show.php?p=56&a=7


Więcej informacji o liście dyskusyjnej pld-devel-pl