KDE a sdilene knihovny ( was Re: Programovani - shared libraries na Linuxu )
Lubos Lunak
l.lunak na sh.cvut.cz
Čtvrtek Září 6 23:07:44 CEST 2001
Lubos Lunak wrote:
> Pavel Kankovsky wrote:
>
[snip]
>
>>> ale myslim, ze uz dalsimi detaily nebudu obtezovat.
>>Jen obtezujte, myslim, ze mnoho z nich bude zajimavych. (Kdyby nic
>>jineho, rozptyli se tim nektere hluboce zazite myty...)
>
>>>Jednak bych pri tom asi hodne nadaval, a tady s tim asi stejne nikdo nic
>>>neudela ( nebo snad ano ? ).
>>
>>A proc ne? Preci jen z .cz pochazi nezanedbatelne mnozstvi lidi, kteri
>>na kernelu, dynamickem linkeru, glibc, gcc ci XFree pracuji, a mnozi
>>z nich (mne nevyjimaje) by radi prilozili ruku k dilu a nektere z techto
>>problemu prozkoumali blize.
>
> A, skvele, tak se hlaste co kdo umite a stavte se do rady, ja porozdeluju
> ukoly :). Vazne, jestli se tedy nekdo hlasi, tak si zatim poreagujte na
> tohle, ja si tu zatim zkusim dat dokopy poznamky.
>
No, jak ted po sobe znovu ctu tyhle dve vety, to se da vylozit vselijak,
doufam, ze si to nekdo nevylozil spatne. Chtel jsem tim rict asi to, ze
jsem ani necekal, ze by se nejaka pomoc u tohohle dala najit i tady, a budu
jedine rad, pokud se tu najdou lide, co temhle vecem rozumi lip nez ja, a
budou ochotni pomoct.
Takze, pro pripadne zajemce jsem z poznamek zatim slozil dohromady tohle
nize o knihovnach. Predem musim jeste jednou zduraznit, ze mam o tehle
vecech jen povrchni znalosti, vim treba, k cemu je .got, ale uz ne, jak to
presne pracuje. Z cehoz vyplyva, ze tohle dole mohou byt pekne neznalkovske
fantasmagorie (ale za temi cisly si stojim).
Vezmeme si treba tu uz zminovanou libkdeui. Jak uz jsem rikal, kdyz se
udela smesny programek
#include <unistd.h>
int main() { sleep( 10 ); }
a prelozi se g++ -O2 test.cpp -lkdeui -L/opt/kde2/lib , a potom se znova
prelozi g++ -O2 test.cpp -lkdecore -L/opt/kde2/lib , tj. v tom prvnim
pripade oproti tomu druhemu je navic prilinkovana jen prave libkdeui, dela
rozdil nesdilene pameti zhruba 100kB (at uz podle top, nebo podle
/proc/<pid>/maps). Zkousel jsem i gcc-2.96 i gcc-2.95.x, u obou ty vysledky
jsou zhruba stejne, u gcc3.x to pocitam nebude jinak.
Na http://dforce.sh.cvut.cz/~seli/download/libkdeui_objs_objdump.txt.bz2
jsem dal vypis z prikazu objdump --headers pro vsechny .o soubory pro
libkdeui, na
http://dforce.sh.cvut.cz/~seli/download/libkdeui_objdump.txt.bz2
je objdump --headers pro vyslednou libkdeui. Abych se nikdo nemusel
opakovat s mym pocitanim, sekce .data a .bss v *.o daji dohromady neco
kolem 4kB plus minus, rozhodne to neni pres 10kB ( a jak jsem rikal,
globalnich objektu s konstruktorem je minimum ). Na druhou stranu .data je
v libkdeui.so kolem 75kB a spolu s 19kB .got tvori vetsinu non-readonly dat
v libkdeui. Nebudu napinat, staci na ten vypis objdump pro *.o dat grep
'__vt_' a ty vtables se na tech cca 70kB slozi v pohode ( QWidget ma vtable
400 bytu, takze ono se to slozi - a ne, nemyslim, ze tech virtualnich metod
v QWidget je nejak moc, i kdyz o tom by se mozna dalo diskutovat ).
Protoze ty vtables samozrejme v PIC kodu potrebuji relokace odkazu na ty
jednotlive metody, jdou do .data segmentu jako potencionalne nesdilene, a
pri nahravani te knihovny do pameti tak samozrejme skonci, az se po nich
probehne dynamicky loader a bude do nich zapisovat ty relokovane odkazy.
Ted by se mozna hodila filozoficka diskuze o tom, proc by se melo vykaslat
na PIC a delat to jako na MS Windows, viz. ta moje uplne prvni kousava
poznamka, ale ted bude asi lepsi neco udelat s tim PIC kodem.
Reseni me napadaji asi tak tahle ( pozor, autor si to nejspis predstavuje
jak Hurvinek valku) :
1) Zmenit implementaci vtables v gcc tak, at to negeneruje takova kvanta
nutne relokovanych dat. Tohle uz se asi pocitam na gcc mailing listu
probiralo v dobe, kdy Waldo Bastian napsal
http://www.suse.de/~bastian/Export/linking.txt . Jeden clovek pro KDE
napsal hack, ktery .o soubory pred jejich vlozenim do knihoven upravi tak,
ze ve vtables zmeni drahe relokace na lacinejsi za cenu jedne indirekce pri
volani navic; ja tomu tedy moc nerozumim a ani jsem to nezkousel, ale odkaz
na to je http://www.research.att.com/~leonb/objprelink/ .
2) IMHO lepsi reseni - spolehnou se na prelink od Jakuba Jelinka. Vzhledem
k tomu, ze ten .data segment knihovny je namapovan copy-on-write, takze
kdyby se vubec zadne relokace neprovadely, zadne duplikovani stranek nebude
a je to prakticky nastejno, jako kdyby ty vtables skoncily v .rodata.
Skutecne to tak i trochu funguje, treba po prelinkovani s libkdeui uz to
neni 100kB nesdilene pameti, ale jen asi 50kB. Mohlo by to byt nejpis i
lepsi, ale jsou tu dva problemy :
- Tzv. konflikty pri prelinkovani, bylo mi to vysvetleno asi takhle :
libta.so: obsahuje foo() { return 1; }
libtb.so: obsahuje bar() { foo(); }, slinkovane s -lta
libtc.so: obsahuje foo() { return 2; }, slinkovane s -ltb
Kdyz se prelinkuje libtb, tak foo() je odkaz do libta, jenze kdyz se
prelinkuje libtc, tak foo() musi byt odkaz do libtc. Takze pri spousteni
programu slinkovaneho s libtc se tehle odkaz stejne musi relokovat, a to
samozrejme znamena zapis a duplikaci prislusne pametove stranky.
Pri spusteni programu prelinkovaneho s libkdeui a jen s libkdecore je
rozdil relokaci ( hlaseny pomoci LD_DEBUG=statistics ) 185, coz nejspis
vsechno pada prave na konflikty. Kdyz tedy vezmeme, ze prelinkovana
libkdeui ma porad 52kB nesdilene pameti a rekneme 8kB padne na oltar
dynamickemu loaderu, porad tu tech 185 relokaci zpusobi duplikaci 11x 4kB
pametovych stranek.
Tohle je obzvlast zabavnejsi o to, ze libkdeui zadne takovehle prepisovani
symbolu jako v tom vypisu s libtc nedela ( rozhodne ne pro 185 symbolu ).
Moje teorie je takova, ze gcc si obcas moc nevi rady s vecmi jako sablony,
virtualni tabulky, inline metody a podobne a pro jistotu je v nekterych
pripadech emituje vic nez jednou. Sice jsou jako LINK_ONCE_DISCARD, takze ve
vysledne knihovne jsou tyhle symboly jen jednou, ale linker uz se neobtezuje
(nebo mozna ani nemuze) podivat, ze tyhle symboly uz existuji v nejake
knihovne, s kterou se tahle vysledna linkuje. Takze vsechny veci jsou pekne
zduplikovane a dopadne to jak s tou libtc nahore.
Konkretne by me zajimalo, jestli by mi nekdo dokazal popsat pravidla pro
emitovani tehle veci, nektere veci v Qt a kdelibs by se urcite daly upravit
tak, ze gcc je nebude emitovat vice nez jednou. Konkretne kdyz treba ten
vyse zminovany vypis objdump na *.o se prozene pres 'grep '__vt_' | sort
+2', tak je dobre videt, ze treba vtable pro QCString je tam asi dvacetkrat
(i kdyz pro konflikt stejne nejspis staci, ze v libkdeui se ta vtable
vytvori aspon jednou a tim nahradi vtable vygenerovanou v libqt ).
Konkretne ten QCString funguje asi na tomhle principu :
class A
{
public:
virtual ~A();
};
class B
: public A
{
// tady naprosto zadna virtualni funkce
}
Zkousel jsem si tenhle zjednoduseny priklad, jakykoliv .cpp, ktery udela
#include na tyhle dve tridy a pak vytvori instanci tridy B, obsahuje vtable
pro B, protoze gcc tak nejak netusi, kam s tim. Podobne casto pohori na
sablonach a inline funkcich (mimochodem, ta dokumentace o tom, jak se
generuji tyhle veci v info dokumentaci k gcc3.0 je jeste porad tak z dob
egcs, ne ?)
Kdyz tak nad tim jeste premyslim, nejde nejakou tou LD_xxx promennou
zjistit, presne ktere symboly zpusobuji konflikty ?
- Druhym problemem je to, ze gcc, co jsem se dival, se s tim moc nestve a
rve vsechny veci do .data jak ho zrovna napadne (to znamena zkratka tak, ze
je tam dava za sebou jak mu pri prekladu ty veci prijdou pod ruku). Kdyz se
vezme kod jako
int pos = 0;
const char* const txt[] = { "a", "b" };
tak txt[] muze po prelinku zustat teoreticky bez zapisu (podle Jakuba
Jelinka jsou tohle R_*_RELATIVE relokace a ty nikdy nezpusobi konflikt). Na
druhou stranu, ty dve veci budou v pameti hned za sebou a staci nekde
udelat 'pos = 10' a cela pametova stranka jde do haje (resp. tedy je
duplikovana), vcetne naseho teoreticky sdileneho konstatniho txt[]. Ted se
staci podivat na kod vygenerovany pomoci MOC z Qt3, tam se neco takovehleho
generuje, jedna zapisovatelna promenna a halda konstatnich dat, ktere ale
potrebuji relokaci, takze klidne muzeme hadat, kolik tech pametovych
stranek se bude duplikovat vcetne teoreticky konstatnich sdilenych dat.
Dovolim si tipovat prvni, muj odhad zni 'hodne'.
Moje takova predstava o reseni tohohle by bylo, ze gcc pri generovani dat,
ktere by sly v non-PIC kodu do .rodata ale v PIC musi do .data, by nesly do
.data, ale do nejakeho nazveme ho treba .almostrodata. Tenhle .almostrodata
by mel stejne flagy jako .data (CONTENTS, ALLOC, LOAD, DATA, zadne
READONLY), ale linker by pri vytvareni knihovny posbiral .data a naskladal
je v knihovne za sebe, pak by posbiral .almostrodata a ty poskladal za
sebe. Bingo, v tehle teorii zapisy do promennych duplikuji stranky, ktere
obsahuji jen promenne, ale ne konstatni data. Ted jeste jak se tahle teorie
lisi od praxe (a to ja nevim).
No, to by bylo pocitam k tomuhle tematu ode me zatim vsechno. Ted by me
potesilo, kdyby to nekdo veci znaly fundovane okomentoval.
Lubos Lunak
--
l.lunak na email.cz ; l.lunak na kde.org
http://dforce.sh.cvut.cz/~seli
Linus Torvalds :
'No fake - I'm a big fan of konqueror, and I use it for everything.'
Další informace o konferenci Linux