Speicherbedarf eines Prozesses

Jan 'RedBully' Seiffert redbully at cc.hs-owl.de
Thu Apr 22 23:07:49 CEST 2010


Sascha Effert schrieb:
> Am Mittwoch, den 21.04.2010, 17:01 +0200 schrieb Florian Lohoff:
[snip]
>> Die RSS ist ja nur ein real life parameter der den best case darstellt.
>> wenn dein system memory pressure hat wird das ja weniger. Besser ist es
>> die VSZ  sich anzuschauen - das ist ja eher die worst case betrachtung.
>>
>> Ansonsten mal valgrind ansehen - ich meine der spuckt auch stats aus,
>> die beinhalten aber IIRC nur malloc d.h. nicht dein text segment deiner libs.
>>
>> Flo
> 
> O.K., danke für die vielen Antworten. Mir war nicht bewusst, dass das so
> ein Problem ist. In Java ist das irgendwie einfacher... :-)
> 
> Ich werde weiter per ps den Prozess scannen, bessere Werte scheine ich
> auch aus dem Prozess selbst heraus nicht zu erhalten ohne erheblichen
> Aufwand in das mitzählen bei jedem new/malloc und der Berechnung des
> Verbrauchs auf dem Stack zu benötigen.
> 
> Als Parameter werde ich VSZ verwenden. Ich hatte tatsächlich vorher
> nicht realisiert, dass RSS nicht den geswapten Speicher mit
> einberechnet. Sehe ich es richtig, dass auf einem Rechner ohne Swap
> immer RSS=VSZ gilt? Ich bin ein wenig verwundert, denn auf meinem gerade
> gestarteten Notebook erhalte ich für Evolution RSS 50664 (ca. 50 MB) und
> VSZ 198364 (ca. 200 MB). top sagt mir aber, dass ich momentan keinen
> SWAP (0 k) verwende. Kann mir das jemand erklären?
> 

Der Grund, warum ich eigentlich auch erst Florians Hinweis auf die VSZ so nicht
stehen lassen wollte, ist dass die VSZ IMHO nicht sooo viel aussagt.
Ja, sie ist ein Worst-Case-Mass.
Aber ein seeehr grobes.

Die VSZ ist nur die "groesse" des virtuellen Adressraums, dieser muss nicht mit
irgendwas unterfuettert (a.k.a. echtem Speicher, RSS & SHR) sein.
Es gibt da zwar eine enge Korrelation, aber es gibt genug Situationen in denen
einfach ein riesen Adressraum da ist, aber nichts dahinter.
mmap eine grosse Datei im Stueck und fass nur ein bischen davon an oder schieb
mit sbrk deinen Heap um 5MB hoch (z.B. default inkrement um nicht so oft sbrk
aufzurufen), bumm, deine VSZ ist sofort einen ganzen Sack groesser.

Klassische Beispiele sind Virtuelle Maschinen/Interpreter, die erstmal ein
grossen "Spielplatz" anlegen, oder Emulatoren, oder Wine (versucht den
Adressraum aussehen zu lassen wie unter Windows, da hat man schnell VSZ=2G, bei
RSS=30M).

Aber auch duenn besiedelte grosse Strukturen oder einfach Fragmentation koennen
dazu fuehren (Du kannst die VSZ "hinten" nicht verkleinern weil da noch ein
Objekt lebt, aber du kannst die Speicherseiten dazwischen schon mal mit
madvise(WONT_NEED) zurueckgeben).

Bei Evolution, ich wuerd sagen der uebliche C++ bloat mit gaaanz viel
Allokationen (man hat ja endlich richtige Strings, in Containern...).

Schau dir z.B. mal den neuen Firefox mit jemalloc in htop an. Auch der belegt
riesige mengen VSZ (hier grad 380M), aber (wegen jemalloc) um die Fragmentation
zu senken (dynamische Skriptsprache integriert, viel Grafik gedoense, GUI ist in
Javascript, viel Textverabeitung, Berge von Libs, Plugins), so das die RSS
schoen rauf und runter gehen kann, je nachdem wieviel Speicher grad _wirklich_
gebraucht wird.

Mit anderen worten:
VSZ ist der max. moegliche Speicherverbrauch (aka Adressraumgroese)
RSS ist der tatsaechliche eigene Speicherverbrauch
SHR ist der teilbare Speicherverbrauch, er muss aber nicht geteilt sein

Wenn was rausgeswapt wird, werden AFAIK RSS und SHR kleiner.
Leider ist SWP AFAIK nicht einzeln fuer einen Prozess verfuegbar.

Das Speichermanagement ist eben kompliziert (im Kernel und in guten Allokatoren)
um aus dem Speicher in einer Maschine das "maximum" (klar, koennte immer alles
noch viel besser sein) rauszuholen.
(minus Hardware beschraenkungen, wenn deine kleinste Speicherseitengroesse 64k
ist (PowerPC?), dann kannst du nur in 64k Schritten das ganze verwalten. Kleine
Seiten haben aber mehr overhead, siehe hugepages fuer z.B. Datenbanken und so
feaky high perfomance teuer business stuff, der mal kurz seine eigene
Speicherverwaltung mitbringt, um genau den overhead zu senken da man eh weiss
das man vieeel Speicher braucht)

Der Kernel kennt mehrere Arten und zustaende von Speicher, eingeteilt in
mappings (siehe /proc/pid/smaps) denen er "echte" Speicherseiten zuordnet (oder
entfernt). Mal ein paar Begriffe:
- Anon und File
File ist eine Datei in den Speicher ge-mmap-t. Da die CPU die Festplatte nicht
direkt adressieren kann, muss das mit Hilfe von "echtem" RAM passieren (Datei in
RAM laden, Prozess(or) zur verfuegung stellen).
Anon ist Speicher, der keine Datei dahinter hat, ausser swap.
- shared und private
shared Speicher bedeuted, das die gleiche Speicherseite in mehreren Prozessen
"vorhanden" ist (oder sein kann), und falls drauf geschrieben wird, das auch in
allen Prozessen sichtbar ist.
private bedeutet das aenderungen nur in diesem Prozess sichtbar sind.
- clean und dirty
Speicherseiten die "clean" sind, sind Seiten die so 1:1 dem backing store
entsprechen, sie sind quasi nur gecached, damit die CPU die Daten ansprechen
kann. Wenn Speicher gebraucht wird, koennen diese Seiten einfach verworfen
werden, man kann sie ja so wieder von irgendwoher laden.
Speicherseiten die dirty sind, sind Seiten die so nicht mehr dem backing store
entsprechen, die muessen gesichert werden, bevor sie fuer irgendwas anderes
benutzt werden koennen.
- R W X
ist das mapping Les-, Schreib-, Ausfuerbar


So, damit laesst sich jetzt ein bisschen was beschreiben:
Z.B. dein Heap (im Sinne von sbrk).
Er ist ein linearer Bereich (mapping) aus privatem Anon Speicher.
Solange du den Speicher nicht beschreibst, ist er clean. Solange sind (zumindest
wenn overcommit an ist) auch keine echten Speicherseiten daran gebunden.
Du kannst problemlos ein malloc(100*1024*1024) machen, dein VSZ wird um 100MB
steigen, aber deine RSS erstmal nicht (wir vergessen jetzt mal grade prefaulting).
Beim lesen "siehst" du immer die gleiche Speicherseite (eine Zero Page).
Erst beim schreiben (pagefault) bekommst du eine "eigene" Speicherseite, sie ist
danach dirty (dein RSS steigt). Wenn der Kernel diese Seite fuer was anderes
braucht, muss er sie erst clean bekommen, da es kein File backing gibt, kann sie
nur in den Swap wandern.

Da das ganze ein linearer Bereich ist, der nur laenger (hinten mehr Raum fuer
mehr moegliche Speicherseiten) oder kuerzer (Speicherseiten hinten wieder
freigeben) werden kann, hast du das Problem dass Fragmentation dazu fuert, das
du den heap nicht verkuerzen kannst, also Speicherseiten "in der Mitte" nicht an
den Kernel zurueck gegeben werden koennen.
Frueher hat sich das durch swappen geloest, der Kernel hat dann Seiten in den
swap geschrieben, die der Prozess so selber nicht mehr braucht (sie waren
"kalt", der Prozess hat sie lange nicht benutzt, beste swap "opfer"), unnoetig,
aber egal, es lief.
Heutzutage kann ein guter Allocator da mit madvise(WONT_NEED) Loecher in den
heap stanzen. (Problem ist, dass das nur auf Seitengranularitaet funzt, wenn der
Heap also schlimm fragmentiert ist hast du auf jeder Speicherseite noch
irgendein nn Byte Objekt, Allokatoren versuchen das mit schlauen Algorithmen zu
vermeiden).

Man kann aber Anon Speicher auch ueber mmap bekommen. Der Vorteil ist: So
bekommt man ""kleine", _einzelne_ Stuecke Speicher (mappings), die man einzeln
eben auch wieder freigeben kann.
Der Nachteil ist, dass das einen grossen overhead hat.

Darum benutzen Allokatoren oft eine Mischung aus sbrk und mmap (z.B. kleine
Objekte auf klassischem Heap, Objekte > x per mmap). Oder sie benutzen mmap
exklusiv um viele mittelgrosse Stueckchen zu bekommen, in denen sie dann lauter
kleine heaps "aufmachen", ist so ein heap wieder leer, kann er problemlos
einzeln wieder freigegeben werden. Oder Oder Oder.

Jetzt kann man dank mmap auch mit Dateien rumspielen.
Wenn du eine Datei mmap'st, dann steigt dein VSZ auch erstmal um die
Dateigroesse (bzw. mapping laenge). Denoch belegt das so erstmal keinen Speicher
(ok,ok, minus Kernel overhead etc.).
Du kannst dir aussuchen ob das mapping shared oder private ist, ob es les-,
schreib- und/oder ausfuerbar ist.

Ist es shared, bedeuted das, dass aenderungen (du schreibst in den gemapten
Speicher) auch wirklich in die Datei zurueck geschrieben werden. Dieses mapping
hat quasi sein eigenes swapfile. Jeder Prozess der die Datei shared mapped, hat
die gleichen Speicherseiten und sieht die aenderungen der anderen Prozesse.

Ist es private, bedeuted das, das aenderungen _nicht_ in die Datei zurueck
geschrieben werden. Denoch kannst du (so das mapping nicht read only ist) da
reinschreiben.
Was passiert denn da? Die zu der Datei gehoerenden Seiten sind erstmal ge-shared
und read only. Wenn du in eine Seite reinschreibst, erzeugt das einen pagefault
(da Seite RO), der Kernel sieht dann das du da doch reinschreiben darfst, sucht
eine freie Seite, kopiert den orig. Inhalt da rein, und gibt dir diese Seite
dann als RW (COW, Copy on write), du kannst deinen Schreibvorgang beenden. Jetzt
ist in dem ganzen mapping (vielleicht mehrere MB, SHR), eine Seite Anon, dirty
(RSS).

Executables und Libs werden heute auch so behandelt:
Wenn du z.B. eine .so benutzt, so wird sie vom loader (meist /lib/ld.so) per
mmap in den Speicher geholt. Das ist File backed. Zum Ausfuehren muss das
natuerlich erst in echtes RAM geladen werden. Denoch, falls die Seiten knapp
werden, kann der Kernel problemlos diese Seiten weg werfen, da sie File backed
und meist clean sind (und mit ein Grund warum bei Speicherknappheit X so derbe
ruckelt. Der X Binaercode wird aus dem Speicher geschmissen... ).
Das ganze mapping der Lib ist zwar als privat markiert, aber solange keiner in
den Binaercode reinschreibt, teilen sich alle Prozesse die diese Lib benutzen
die gleichen Speicherseiten.
In den Binaercode wird auch normalerweise nicht geschrieben (Read Only gemappt),
denoch kann es konstruktionen geben wo das noetig ist (Text relocationen), in
dem Fall wird das shared mapping gebrochen, der Prozess bekommt seine eigene
Seite, in der nur er seine Aenderung sieht (COW).
Danach ist diese Seite dirty. Das ist schlecht.
Um das zu verhindern wird eine Executable/Lib in mehrere Sektionen unterteilt,
um Stellen die sich einfach mappen lassen (.text, .rodata) in eigenen mappings
von denen zu trennen, in die reingeschrieben werden muss (.got, .plt).

Wie du siehst, Speicherseiten gehen hin, her, das geht rubbel-die-Katz. Mal wird
mehr geladen weil Speicher uebrig ist (readahead), mal werden dir hinten rum
Seiten gemopst. Und das ist nur der Kernel, was die runtime libs so machen, ist
eben noch mal ein ganz eigener Film. Ganz oben sitzt dann dein Programm und tut
irgendwas (was das positiv oder negativ beeinflusst).
Deshalb ist es so kompliziert zu sagen, was da jetzt wie _warum_ wieviel
Speicher braucht.

So, ich hab jetzt keine Lust mehr das 20 mal Korrektur zu lesen.

Wenn die VSZ gross ist, dann gilt es erstmal rauszufinden warum sie so gross
ist. Dann kann man abwaegen ob das ein Problem ist, oder nicht.

Die besten Chancen hast du da noch mit valgrind (oder zumindest mem_usage um das
etwas besser aufgeschluesselt zu bekommen als VSZ, RSS, SHR).

> tschau
> 
> Sascha
> 
Gruss
	Jan

-- 
<zindhietche> In England wurde ein Lehrer festgenommen. Bei ihm wurden ein
Bleistift, ein Lineal und ein Geodreieck gefunden. Angeblich war er Teil
des Al-Gebra Netzwerks und besaß "weapons of math instruction"



More information about the Linux mailing list