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