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