Ist eigentlich in Sprachen wie C und C++ auf modernen Betriebssystemen
Speicherfragmentierung kein Thema mehr? Wenn ja, wieso nicht? Ich meine
jetzt keine Memory-Leaks, sondern es werden viele Objekte
unterschiedlicher Größe allokiert und freigeben. Oder sollte man im Code
da immer noch gegensteuern (verschiedene Memorypools einrichten usw.)?
Jörg W. schrieb:> Sowas machen ordentliche malloc()-Implementierungen von sich aus.
Gut zu wissen. Wie findet man heraus ob eine ordentliche malloc
Implementierung vorliegt?
Peter II schrieb:> spätestens beim realloc wird es aber schwer.
Nö. Die Implementierungen haben ja durch ihr Pooling in vielen
Fällen genug Reserve innerhalb des jeweiligen Pools, um das realloc
sofort befriedigen zu können.
lalala schrieb:> Wie findet man heraus ob eine ordentliche malloc Implementierung> vorliegt?
Sourcecode ansehen.
lalala schrieb:> Welchen, den z.B. von Windows oder den von Visual Studio?
PGH – Pech gehabt. ;-)
Dann kannst du nur hoffen, dass deren Autoren auf der Höhe der Zeit
sind und vergleichbar gute Implementierungen liefern wie die, die
du dir im Opensource-Umfeld alle ansehen kannst.
Jörg W. schrieb:>> spätestens beim realloc wird es aber schwer.>> Nö. Die Implementierungen haben ja durch ihr Pooling in vielen> Fällen genug Reserve innerhalb des jeweiligen Pools, um das realloc> sofort befriedigen zu können.
wenn ich aber immer erst 1 Byte anfordere und dann mit realloc 10Mbyte
dann liegt es doch im falschen pool? Oder es muss immer umkopiert werden
was aber auch nicht immer vorteilhaft ist.
Jörg W. schrieb:> Dann kannst du nur hoffen, dass deren Autoren auf der Höhe der Zeit> sind und vergleichbar gute Implementierungen liefern wie die, die> du dir im Opensource-Umfeld alle ansehen kannst.
Achso, dann sind die guten Implementierungen die die möglichst viel Zeit
brauchen.
http://locklessinc.com/benchmarks_allocator.shtml
Peter II schrieb:> wenn ich aber immer erst 1 Byte anfordere und dann mit realloc 10Mbyte> dann liegt es doch im falschen pool?
Ja.
> Oder es muss immer umkopiert werden> was aber auch nicht immer vorteilhaft ist.
Was heißt „immer“?
Es muss genau in diesem Falle umkopiert werden, und umkopiert werden
muss ja auch nur das eine Byte.
Ansonsten solltest du dir natürlich mal deine Strategie überdenken.
Sonderlich sinnvoll klingt das nicht. Wenn du anfangs noch nicht
weißt, was du überhaupt brauchst, dann lass den Pointer einfach
erstmal bei NULL, damit kommt realloc() problemlos zurecht (es mutiert
dann zum malloc()). Ein Byte „nur mal prophylaktisch“ zu allozieren,
hat jedenfalls keinen großen Sinn.
Jörg W. schrieb:> lalala schrieb:>> Welchen, den z.B. von Windows oder den von Visual Studio?>> PGH – Pech gehabt. ;-)>> Dann kannst du nur hoffen, dass deren Autoren auf der Höhe der Zeit> sind und vergleichbar gute Implementierungen liefern wie die, die> du dir im Opensource-Umfeld alle ansehen kannst.
Da hab ich so meine Zweifel bei Visual Studio, da das bei C bekanntlich
alles andere als auf der Höhe der Zeit ist.
Peter II schrieb:> Jörg W. schrieb:>>> spätestens beim realloc wird es aber schwer.>>>> Nö. Die Implementierungen haben ja durch ihr Pooling in vielen>> Fällen genug Reserve innerhalb des jeweiligen Pools, um das realloc>> sofort befriedigen zu können.>> wenn ich aber immer erst 1 Byte anfordere und dann mit realloc 10Mbyte> dann liegt es doch im falschen pool? Oder es muss immer umkopiert werden> was aber auch nicht immer vorteilhaft ist.
Das wird es meistens sowieso, denn oft ist ja direkt danach gar nicht
genug Platz frei. Und 1 Byte umzukopieren dauert auch nicht so extrem
lange. ;-)
Jörg W. schrieb:> Ansonsten solltest du dir natürlich mal deine Strategie überdenken.> Sonderlich sinnvoll klingt das nicht.
war ja nur als Beispiel gedacht. Mit einer ungünstigen Programmierung
ist auch beim einen guten malloc Speicherfragmentierung nicht
verschwunden.
Rolf M. schrieb:> Da hab ich so meine Zweifel bei Visual Studio, da das bei C bekanntlich> alles andere als auf der Höhe der Zeit ist.
Andererseits ist eine effiziente Speicherallozierung auch im Sinne
von C++, und da sind sie meines Wissens nicht so hinterwäldlerisch
wie bei C. Letztlich ist "new" ja weiter nichts als ein Wrapper um
malloc() (mit nachfolgendem Aufruf der Konstruktoren natürlich).
lalala schrieb:> Ist eigentlich in Sprachen wie C und C++ auf modernen Betriebssystemen> Speicherfragmentierung kein Thema mehr?
Man sollte hier auch die Formulierung "auf modernen Betriebssystemen" in
der Frage berücksichtigen. Auf pagenden Systemen sollte selbst eine
tatsächlich existente Speicherfragmentierung kein Thema sein. Wenn
solche unbenutzten Speicherfragmente entstehen sollten, werden die
entsprechenden Memory-Pages einfach irgendwann rausgeswappt. Wenn der
virtuell vorhandene Speicherraum wesentlich größer ist als der
physikalische, entsteht da auch kein Problem durch Fragmentierung.
Grundsätzlich brauchen malloc (), free () und Konsorten auch Zeit.
Allerdings sollte man diese auch relativ sehen.
Es besteht ein riesiger Unterschied dazwischen ob in einer Schleife
100000-mal Platz für einen Zeiger reserviert wird oder ob am Anfang der
Arbeit Platz für eine Inputdatei geschaffen wird.
Noch schlimmer sind die Komfortfunktionen für die Vergrößerung eines
einmal reservierten Bereichs.
Der zeitliche Aspekt der Reservierungsfunktionen wird aber fast immer
unter den Tisch gekehrt, obwohl jedem klar sein sollte, dass das
Einbinden eines Speicher(bereich)s in eine Speicherverwaltung aufwändig
ist. Auch wenn selbige einen, vom Betriebssystem unabhängigen "Puffer",
verwendet.
Der Gedanke, dass es kein Problem mit der Fragmentierung des Speichers
mehr gibt, kommt daher, dass die wenigsten Programme so speicherintensiv
sind, dass das Ganze ein Problem wird. Oft bekommt man auch nichts davon
mit, weil das Betriebssystem einfach weiteren Speicher "erzeugt". Das
dann, eventuell beginnende Auslagern auf die Platte, geht meist in dem
allgemeinen Plattengedöns unter.
Ich würde mich an folgendem Leitfaden orientieren. Wird, wie oben gesagt
sehr oft ein kleiner Bereich reserviert - und auch wieder freigegeben -
so sollte es auch keine Probleme geben. Sollten aber die reservierten
Blöcke zwischen 1 und 100000 Bytes variieren, so sieht das ganze schon
ganz anders aus.
Wie man sehen kann, habe ich nicht allzu viel Vertrauen in die
betriebssystemeigene Speicherverwaltung. Bei sehr großen Blöcken sieht
das Ganze aber schon anders aus. Da kann sogar die systemeigene
Speicherverwaltung eine Art Defragmentierung vornehmen.
Amateur schrieb:> Wie man sehen kann, habe ich nicht allzu viel Vertrauen in die> betriebssystemeigene Speicherverwaltung.
Tja, was man nicht versteht, dem traut man nicht?
Das ist wie mit printf(): ich wette, dass die meisten derjenigen,
die sowas „aus Prinzip“ (also nicht infolge eigener Untersuchungen
und Messungen) ablehnen, in aller Regel mit ihren „selbstgestrickten“
Alternativen deutlich ineffizienter sein werden als die regulären
Implementierungen. Um letztere hat sich nämlich jeweils jemand recht
intensiv Gedanken gemacht.
Jörg W. schrieb:> lalala schrieb:>> verschiedene Memorypools einrichten>> Sowas machen ordentliche malloc()-Implementierungen von sich aus.
macht das denn tatsächlich malloc selbst?
Ich hätte erwartet, dass dies die Speicherbeschaffung einfach an das OS
weiterdelegiert.
Auf PCs ist Speicherfragmentierung in der Tat spätestens seit 64bit kein
Problem mehr, weil der Adreßraum so riesig ist. Das hat übrigens nichts
mit der Menge an physisch bestücktem Speicher zu tun, das mappt die CPU
schon selber.
Auf Mikrocontrollern und dann auch noch ohne MMU ist
Speicherfragmentierung ein Problem, und deswegen untersagt MISRA ja auch
malloc und Konsorten. Typischerweise alloziert man sich seine Sachen
einfach statisch und sieht dann schon im Mapfile, ob der Speicher
ausreicht.
Braucht man wirklich und echt unverzichtbar dynamische
Speicherverwaltung auf einem Mikrocontroller, dann ist eine Möglichkeit,
die angeforderte Speichermenge beim Allozieren immer zur nächsten
Zweierpotenz aufzurunden. Damit entsteht keine Fragmentierung, und es
läßt sich trivial nachweisen, daß nie mehr als 50% verschwendet werden.
Und printf braucht man allenfalls, wenn man tatsächlich floats auf einem
Controller in Ausgabestrings packen muß, was selten ist. Eine
Konvertierung von Integer nach ASCII ist hingegen trivial zu schreiben.
@Jörg Wunsch
>Amateur schrieb:>> Wie man sehen kann, habe ich nicht allzu viel Vertrauen in die>> betriebssystemeigene Speicherverwaltung.>Tja, was man nicht versteht, dem traut man nicht?
Du musst es ja (besser) wissen.
Vlad T. schrieb:> Ich hätte erwartet, dass dies die Speicherbeschaffung einfach an das OS> weiterdelegiert.
das OS vergibt immer nur Pages (4k). Der Overhead bis zu kernel ist für
einfache Malloc viel zu groß, dann muss schon die runtime vom Prozess
machen.
Vlad T. schrieb:> ...
Ergänzung:
wenn man sich den crt-Code des MSVC anschaut (ja, der ist in jeder
Studioauslieferung dabei) da ist es auch tatsächlich so, dass einfach
die WinAPI Funktion HeapAlloc gecallt wird.
Vlad T. schrieb:> Jörg W. schrieb:>> lalala schrieb:>>> verschiedene Memorypools einrichten>>>> Sowas machen ordentliche malloc()-Implementierungen von sich aus.>> macht das denn tatsächlich malloc selbst?
Nur, dass du mal eine Idee bekommst, was einem bei einer aktuellen
malloc()-Implementierung so erwartet:
Jörg W. schrieb:> Nur, dass du mal eine Idee bekommst, was einem bei einer aktuellen> malloc()-Implementierung so erwartet:
was fürn compiler ist das?
Ich meine, klar, dass für nen Bare-Metal-Compiler da selbst was
gestrickt werden muss, aber warum sollte man unter nem OS das am OS
vorbei machen?
Vlad T. schrieb:> Vlad T. schrieb:>> ...>> Ergänzung:> wenn man sich den crt-Code des MSVC anschaut (ja, der ist in jeder> Studioauslieferung dabei) da ist es auch tatsächlich so, dass einfach> die WinAPI Funktion HeapAlloc gecallt wird.
wobei HeapAlloc noch zur Heapverwaltung gehört
Es geht dann später auf VirtualAlloc
Amateur schrieb:>>Tja, was man nicht versteht, dem traut man nicht?>> Du musst es ja (besser) wissen.
Ja. Ich habe schließlich erstens selbst mal ein malloc() geschrieben
(in der avr-libc), zweitens mir das angesehen, was andere da so machen.
(Die avr-libc-Implementierung macht allerdings kein Pooling, da so
ein AVR halt nur wenig RAM hat und man dann zu viel verschwendet.)
Ich habe mir auch angesehen, was printf macht, auch mal einen Teil
davon selbst geschrieben. Dmitry Xmelkov hat das dann nochmal
gründlich überarbeitet. Klar, die eigentliche Konvertierung von
ein paar Zahlen ist trivial; das ist sie aber auch im printf().
Der eigentliche Gewinn von printf() ist die Bequemlichkeit des
„Rundrums“, also weitere Strings mit ausgeben, Festlegen der
konkreten Formatierung.
Wenn man wirklich nur ein paar nackte Zahlenkolonnen ausgeben will,
braucht man kein printf(), logisch. Aber auch da bieten die
Bibliotheken fertige Implementierungen für itoa() etc.
Vlad T. schrieb:> Ich meine, klar, dass für nen Bare-Metal-Compiler da selbst was> gestrickt werden muss, aber warum sollte man unter nem OS das am OS> vorbei machen?
weil das OS nur Pages von 4k kennt.
Vlad T. schrieb:> macht das denn tatsächlich malloc selbst?> Ich hätte erwartet, dass dies die Speicherbeschaffung einfach an das OS> weiterdelegiert.
Ich kann da jetzt nur für UNIX/Linux sprechen, aber ich nehme an, dass
es bei Windows zumindest ähnlich ist:
malloc() muss es selbst machen, denn es kann nur Happen per brk() und
sbrk() an einem Stück holen. Es könnte auch theoretisch wieder solche
Blöcke an das Betriebssystem wieder zurückgeben, aber das ist praktisch
unmöglich.
ein Beispiel:
1
ptr1=malloc(10000000);
2
ptr2=malloc(1);
3
free(ptr1);
Die 10 MB kann malloc nicht wieder an das OS zurückgeben. Dazu müsste es
erst nach dem LIFO-Prinzip auch das eine Byte wieder zurückgeben. Das
wurde aber von der Applikation (noch) nicht wieder freigegeben.
malloc() muss daher selbst intelligent agieren, um die 10 MB wieder
effizient zu nutzen.
Aber wie ich oben schon sagte: Die virtuelle Speicherverwaltung auf
modernen Betriebssystem entschärft das Speicherfragmentierungsproblem
erheblich.
Vlad T. schrieb:> Jörg W. schrieb:>> Nur, dass du mal eine Idee bekommst, was einem bei einer aktuellen>> malloc()-Implementierung so erwartet:>> was fürn compiler ist das?
jemalloc (einfach Tante Gugel fragen), in diesem Falle direkt aus
dem FreeBSD-Sourcetree (weil ich den einfach parat liegen habe).
Das wird in diesem Falle also als OS-eigene Variante bei FreeBSD
benutzt.
Peter II schrieb:> wobei HeapAlloc noch zur Heapverwaltung gehört>> Es geht dann später auf VirtualAlloc
genau - und das ist der Teil, der sich um die Effiziente zuteilung
kümmern sollte und VirtualAlloc ist low lewel und page-basierend.
Deshalb die Frage, warum jeder Compiler über die OS-Funktionalität
nochmal was drüberstülpen sollte. Oder ist Windows da so eine Ausnahme,
dass es eine "high-level" Allokationsfunktion gibt?
Vlad T. schrieb:> Deshalb die Frage, warum jeder Compiler über die OS-Funktionalität> nochmal was drüberstülpen sollte.
Du solltest hier „Compiler“ nicht mit „Implementierung“ (im Sinne
des C-Standards) durcheinander würfeln. Zu letzterer gehört halt
die Standardbibliothek, in der auch malloc() drin ist.
Das muss natürlich auf das OS abgestimmt sein, aber an irgendeiner
Stelle musst du zwangsläufig die Arbeitsteilung zwischen beiden
organisieren. Wenn man für jedes einzelne zu allozierende Byte erst
einen Syscall ins OS bemühen muss, wird der Overhead viel zu hoch.
Jörg W. schrieb:> Vlad T. schrieb:>> Jörg W. schrieb:>>> Nur, dass du mal eine Idee bekommst, was einem bei einer aktuellen>>> malloc()-Implementierung so erwartet:>>>> was fürn compiler ist das?>> jemalloc (einfach Tante Gugel fragen), in diesem Falle direkt aus> dem FreeBSD-Sourcetree (weil ich den einfach parat liegen habe).> Das wird in diesem Falle also als OS-eigene Variante bei FreeBSD> benutzt.
okay, das os benutzt diese Bibliothek. Wahrscheinlich wird es doch auch
diese Funktionalität in form eines System-Calls anbieten, oder nicht?
Muss wirklich jedes Tool eine eigene Speicherverwaltung einlinken?
Ein OS-Call macht doch viel mehr sinn, Implementierungs-Fehler werden
unwahrscheinlicher und bei update für alle Programme beseitigt.
Frank M. schrieb:> malloc() muss es selbst machen, denn es kann nur Happen per sbrk() an> einem Stück holen.
Das ist doch eine Frage der Systemschnittstelle.
Vlad T. schrieb:> okay, das os benutzt diese Bibliothek. Wahrscheinlich wird es doch auch> diese Funktionalität in form eines System-Calls anbieten, oder nicht?
Der Syscall dafür ist klassisch sbrk(). Der ändert nur die Größe
des Datensegments.
> Muss wirklich jedes Tool eine eigene Speicherverwaltung einlinken?
Du hast eine falsche Vorstellung von "OS".
Die Standardbibliothek liegt zwar im Userland, aber sie ist natürlich
inhärenter Bestandteil des OSes. (Bei den BSDs, anders als bei Linux,
auch im gleichen SVN-Repository gepflegt wie der Kernel selbst.)
> Ein OS-Call macht doch viel mehr sinn,
Viel zu aufwändig.
Vlad T. schrieb:> Ein OS-Call macht doch viel mehr sinn
Wie Jörg oben schon schrieb:
Wenn ich 100000 mal 1 Byte allokiere, machen diese 100000 OS-Calls
überhaupt keinen Sinn. Ganz im Gegenteil: Sie bremsen das Programm und
das ganze OS aus. Dagegen sind 100000 malloc-Calls überhaupt kein
Problem, wenn dieser daraus lediglich eine Handvoll sbrk()-Calls
generiert.
> Implementierungs-Fehler werden> unwahrscheinlicher und bei update für alle Programme beseitigt.
malloc() und Co sind schon viele Jahrzehnte alt und entsprechend
ausgereift. Du musst nicht meinen, dass da noch alle 14 Tage ein Bug
auftritt.
>> malloc() muss es selbst machen, denn es kann nur Happen per sbrk() an>> einem Stück holen.> Das ist doch eine Frage der Systemschnittstelle.
Eben, und die ist standardisiert - zumindest bei unixoiden Systemen.
Schau Dir brk() und sbrk() an.
Jörg W. schrieb:> Du hast eine falsche Vorstellung von "OS".>> Die Standardbibliothek liegt zwar im Userland, aber sie ist natürlich> inhärenter Bestandteil des OSes. (Bei den BSDs, anders als bei Linux,> auch im gleichen SVN-Repository gepflegt wie der Kernel selbst.)
Moment,
Die C-Standardbibliothek kommt doch vom Compiler und nicht vom OS.
GCC hat seine, MSVC hat seine, Intel hat seine, jeder Cross-Compiler hat
ne eigene.
Was anderes ist die Systemschnittstelle des OS. Bei Windows die WinAPI.
Diese teilt sich auf, auf verschiedene libs (kernel32, user32, gdi32,
...).
`malloc` bringen die Standardbibliotheken der Compiler mit. Und
zumindest im VS 2010 wird der Call direkt weitergeleitet an die
WinApi-Funktion HeapAlloc, die Teil des OS, nämlich der kernel32.dll
ist, und hoffentlich so effizient, wie irgend möglich arbeitet.
Vlad T. schrieb:> Und> zumindest im VS 2010 wird der Call direkt weitergeleitet an die> WinApi-Funktion HeapAlloc, die Teil des OS, nämlich der kernel32.dll> ist, und hoffentlich so effizient, wie irgend möglich arbeitet.
Okay, das mag bei Windows so sein. Bei unixoiden Systemen ist es anders:
malloc() holt sich weiteren Speicher per sbrk() System-Call nur dann,
wenn es diesen auch braucht. Im allgemeinen werden da auch größere
Happen geholt, als die Applikation gerade als Größe dem
libc-malloc()-Call mitgeteilt hat, um die Anzahl von System-Calls zu
minimieren.
System-Calls kosten Zeit - auch unter Windows. Ich halte daher die
Verwaltung von malloc() im User-Space für wesentlich eleganter. Genau
wie es sinnvoll ist, Speicher in größeren Blöcken auf Festplatten zu
verwalten, ist es auch sinnvoller, RAM block-basiert im User-Space zu
verwalten, ohne damit das OS zu belasten.
Frank M. schrieb:> System-Calls kosten Zeit - auch unter Windows. Ich halte daher die> Verwaltung von malloc() im User-Space für wesentlich eleganter. Genau> wie es sinnvoll ist, Speicher in größeren Blöcken auf Festplatten zu> verwalten, ist es auch sinnvoller, RAM block-basiert im User-Space zu> verwalten, ohne damit das OS zu belasten.
HeapAlloc dürfte kein Syscall sein.
Vlad T. schrieb:> Moment,> Die C-Standardbibliothek kommt doch vom Compiler und nicht vom OS.
Keineswegs zwangsläufig.
Ich weiß nicht, wie das bei Windows ist, aber bei den Unixen gehört
dieses zum Betriebssystem. Wenn ich auf diesem FreeBSD etwas mal
mit dem systemeigenen Clang, ein anderes Mal mit GCC compiliere,
wird dennoch das malloc() jeweils aus der Systembibliothek benutzt:
1
$ cat foo.c
2
#include <stdlib.h>
3
4
int
5
main(void)
6
{
7
volatile char *foo = malloc(42);
8
return foo[0];
9
}
10
$ cc -O -o foo foo.c
11
$ ldd foo
12
foo:
13
libc.so.7 => /lib/libc.so.7 (0x80081c000)
14
$ gcc48 -O -o foo foo.c
15
$ ldd foo
16
foo:
17
libc.so.7 => /lib/libc.so.7 (0x80081c000)
> GCC hat seine, MSVC hat seine, Intel hat seine, jeder Cross-Compiler hat> ne eigene.
Ein Cross-Compiler muss natürlich zwangsläufig seine eigene
Bibliothek mitbringen. Diese kann aber (wenn das Target ein OS
hat) wiederum auf das Ziel-OS verweisen.
Wenn ich obiges Beispiel auf dem FreeBSD mit mingw32-gcc compiliere,
dann sehe ich im Mapfile Verweise auf eine libmsvcrt.a, die ein
Wrapper für die entsprechende Windows-Systembibliothek ist. Der
Wrapper kostet dabei keine Performance, er dient nur dazu, den
Inhalt der Windows-DLLs dem GCC so bekannt zu machen, dass der
Linker die entsprechenden Symbole auch einfügen kann. MSVC hat
ähnliche Wrapper, die dann eben nicht auf .a, sondern auf .lib enden.
> `malloc` bringen die Standardbibliotheken der Compiler mit. Und> zumindest im VS 2010 wird der Call direkt weitergeleitet an die> WinApi-Funktion HeapAlloc, die Teil des OS, nämlich der kernel32.dll> ist, und hoffentlich so effizient, wie irgend möglich arbeitet.
Wenn die wirklich die Anforderung jedes einzelnen Bytes als Syscall
an das OS schicken, dann wundert mich es nicht mehr so sehr, warum
beim Compilieren der gleiche Compiler auf gleicher Hardware unter
Windows so viel mehr Zeit braucht als unter Linux oder FreeBSD. :}
Ich habe schon länger nicht mehr den Bedarf gehabt, nachzusehen, aber
zumindest früher hat MS selbstverständlich die Sourcen der mit dem
Compiler mitgelieferten Runtimelibraries ebenfalls mit dem Compiler
ausgeliefert.
(Und jetzt gerade habe ich hier keinen installierten MS-Compiler in
Griffweite, das Büro ist aus und zu, Wochenende!)
Unixe haben ihren Systemcompiler (gcc, llvm oder beides), der auch die
Standardbibliotheken übersetzt hat, die wiederum die entsprechenden
Kernel-Syscalls kennt. Das ist bei Windows (zzgl. der
Binärkompatiblität) auch so. Das Trio aus Kernel, Standardbibliothek und
Compiler gehört grundsätzlich zusammen.
Rufus Τ. F. schrieb:> Ich habe schon länger nicht mehr den Bedarf gehabt, nachzusehen, aber> zumindest früher hat MS selbstverständlich die Sourcen der mit dem> Compiler mitgelieferten Runtimelibraries ebenfalls mit dem Compiler> ausgeliefert.
Interessant; ich habe da vorher noch nie reingesehen, muss ich zugeben.
Frank M. schrieb:> Man sollte hier auch die Formulierung "auf modernen Betriebssystemen" in> der Frage berücksichtigen. Auf pagenden Systemen sollte selbst eine> tatsächlich existente Speicherfragmentierung kein Thema sein. Wenn> solche unbenutzten Speicherfragmente entstehen sollten, werden die> entsprechenden Memory-Pages einfach irgendwann rausgeswappt.
Man kann aber nicht Bereiche beliebiger Größe rausswappen. Ich weiß
nicht, ob heute auch noch mit größeren Pages gearbeitet wird, aber
klassisch ist eine Page typischerweise 4096 Bytes groß.
Speicherfragmentierung findet aber nicht in Blockgrößen von 4096 statt.
Wenn da jetzt nur ein Byte der Page allokiert ist und oft gebraucht
wird, kann die ganze Page nicht ausgelagert werden, auch wenn die
restlichen 4095 Bytes ungenutzt sind.
Wenn dagegen die ganze Page leer ist, dann braucht sie gar nicht
igendwohin gemappt sein, weder auf physischen RAM, noch irgendwo in die
Auslagerung, denn wozu sollte man sich den Inhalt einer komplett
ungenutzten Page extra auf der Festplatte merken?
Nop schrieb:> Auf PCs ist Speicherfragmentierung in der Tat spätestens seit 64bit kein> Problem mehr, weil der Adreßraum so riesig ist. Das hat übrigens nichts> mit der Menge an physisch bestücktem Speicher zu tun, das mappt die CPU> schon selber.
Aber eben nicht Byteweise, sondern 4k-Blockweise. Es wird aber selten
passieren, dass meine Objekte alle exakt 4k groß sind. Die meisten
Objekte werden eher deutlich kleiner sein, und wenn man z.B. an Strings
denkt, dann kann auch die Größe stark variieren.
> Auf Mikrocontrollern und dann auch noch ohne MMU ist> Speicherfragmentierung ein Problem, und deswegen untersagt MISRA ja auch> malloc und Konsorten.
Das ist sicher mit ein Grund, aber da gibt's noch ein paar andere.
Vlad T. schrieb:> Deshalb die Frage, warum jeder Compiler über die OS-Funktionalität> nochmal was drüberstülpen sollte. Oder ist Windows da so eine Ausnahme,> dass es eine "high-level" Allokationsfunktion gibt?
Nö, die gibt's auch auf anderen Systemen und heißt im Falle von
unixoiden Systemen in der Regel malloc().
Vlad T. schrieb:> Jörg W. schrieb:>> Du hast eine falsche Vorstellung von "OS".>>>> Die Standardbibliothek liegt zwar im Userland, aber sie ist natürlich>> inhärenter Bestandteil des OSes. (Bei den BSDs, anders als bei Linux,>> auch im gleichen SVN-Repository gepflegt wie der Kernel selbst.)>> Moment,> Die C-Standardbibliothek kommt doch vom Compiler und nicht vom OS.
Sagt wer? Bei gcc ist keine Standardbilbiothek dabei. Unter Linux ist
die Standardbibliothek die glibc und Teil der Grundinstallation des
Betriebssystems. Ohne die würde kein einziges C-Programm funktionieren,
wodurch man das System nicht mal booten könnte - es sei denn, man würde
alles statisch linken.
> Was anderes ist die Systemschnittstelle des OS. Bei Windows die WinAPI.> Diese teilt sich auf, auf verschiedene libs (kernel32, user32, gdi32,> ...).
Nun, das hängt eben stark vom Betriebssystem ab. Unter Unix-Systemen ist
das die POSIX-Schnittstelle, die (zumindest im Falle der glibc) mit in
der Standardbibliothek steckt und nicht nochmal ein eigenes Layer unter
dieser ist.
warum ich frage: mir ist aufgefallen, das wohl einige Implementierungen
der stl an für mich unerwarteten Stellen (kleine Mengen) Speicher auf
dem Heap allocieren. z.B. gslice. Und da fragt man sich doch ob man sich
da Sorgen machen muss.
lalala schrieb:> Jörg W. schrieb:>> Sourcecode ansehen.>> Hä? Welchen, den z.B. von Windows oder den von Visual Studio?
Man kann gegen beliebige mallocs linken.
Ich kann's mir einfach nicht länger verkneifen:
Pages sind nicht zwingend 4kBytes groß, sondern verfügen über eine
individuell eingestellte Größe. Diese ist zwar oftmals auf 4kBytes
eingestellt, doch kann das auch gerne mal anders sein. 8kBytes je Page,
aber auch andere Werte sind durchaus bei einigen Prozessoren nicht nur
üblich, sondern mitunter sogar Standard.
Zum Beispiel der DEC Alpha war/ist mit 8kBytes bis 4MBytes je Page so
ein Kandidat; typisch waren jedoch 8kBytes. Ebenso verwendet der Intel
Itanium 8kBytes/Page.
Intel x86/64bit(IA64) Prozessoren verwenden typisch 4kBytes, aber auch
2MBytes, 4MBytes oder 1GByte je Page.
ARM Prozessoren verwenden oft 4kBytes/Page, mitunter aber auch mal
64kBytes je Page.
Für frühe Intel x86(386/468/Pentium II?) ließen etwas von bis zu
32MBytes je Page einstellen, wenn ich mich recht erinnere.
Bei den späteren Modellen der x86(IA32) sind Größen von 4kBytes,
2MBytes oder auch 1GByte je Page möglich, und werden/wurden unter
Windows(PAE) teilweise auch so eingesetzt.
Es ist eben Einstellungssache!
Im übrigen ein Grund, weshalb es wenigstens unter UNIX üblich war, und
sicherlich auch heute noch ist, die aktuelle Page-Size aus der
Systemkonfiguration zu lesen.
(siehe: 'int sysconf(_SC_PAGESIZE);' im Header "unistd.h")
Verzichtet man darauf, die konkrete Größe abzufragen, kann man auch
gerne mal ziemlich deutlich daneben liegen.
Unter Windows wird zum Zweck der Abfrage der Seitengröße übrigens der
API-Aufruf 'GetSystemInfo(...);' bzw. 'GetNativeSystemInfo(...);'
verwendet.
Ist wirklich nicht bös gemeint, aber ich konnte es schon allein aus
Passion nicht lassen, diese Anmerkung bezüglich der vermeintlich auf
4kBytes festgelegten PAGESIZE zu verfassen. :-)
Beste Grüße aus Berlin, agk.
Armin G. schrieb:> über eine individuell eingestellte Größe
Naja, das klingt jetzt fast so, als könnte man sich diese beliebig
wählen.
Selbst, wenn es keine feste Größe ist, sind es nur einige wenige
verschiedene Werte, die die Pagesize annehmen kann, und Rolfs
wesentliches Argument (dass man nur seitenweise überhaupt auslagern
kann und nicht ein paar einzelne ungenutzte Bytes) bleibt davon
völlig unberührt.
>der stl an für mich unerwarteten Stellen (kleine Mengen) Speicher auf>dem Heap allocieren. z.B. gslice. Und da fragt man sich doch ob man sich>da Sorgen machen muss.
In dem Moment wo Du irgendwelche Bibliotheken verwendest, sind solche
Effekte "normal". Der ursprüngliche Programmierer stand wohl vor der
Wahl: Soll ich Dir fix einen Speicherbereich "klauen" oder soll ich
diesen nur bei Benutzung reservieren. Noch interessanter wird es, wenn
zur Zeit der Programmierung nicht bekannt ist wieviel denn benötigt
wird.
Noch ein Problem: Eine Bibliothek kann Jahrelang im Einsatz sein und
keine gravierenden Probleme bereiten. Ein kleines Speicherleck, welches
aber unerkannt existiert und immer zwischen Deinen eigenen - natürlich
fehlerfreien - Speicherreservierungen was abzwackt, kann zu recht
interessanten Effekten führen.
Jörg W. schrieb:> lalala schrieb:>> Welchen, den z.B. von Windows oder den von Visual Studio?>> PGH – Pech gehabt. ;-)
Nö. Nur nicht hingeschaut.
Die Quelltexte sind bspw. unter %SystemDrive%\Program Files
(x86)\Microsoft Visual Studio 14.0\VC\crt\src zu finden
> Dann kannst du nur hoffen, dass deren Autoren auf der Höhe der Zeit> sind und vergleichbar gute Implementierungen liefern wie die, die> du dir im Opensource-Umfeld alle ansehen kannst.
Edit: Bevor da jemand weitersucht. Es wird wohl HeapAlloc benutzt
https://msdn.microsoft.com/de-de/library/windows/desktop/aa366597(v=vs.85).aspx
lalala schrieb:> warum ich frage: mir ist aufgefallen, das wohl einige Implementierungen> der stl an für mich unerwarteten Stellen (kleine Mengen) Speicher auf> dem Heap allocieren. z.B. gslice. Und da fragt man sich doch ob man sich> da Sorgen machen muss.
Hab grad selbst was mit gslice gemacht. Da mache ich mir eher Sorgen um
die Berechnungen, die sich daran anschließen, also um die paar
Heap-Anfragen. Nachgemessen habe ich noch nicht, ob es sich auszahlt,
den 3-Zeiler auf mehrere geschachteltet Schleifen umzustellen. Da lass
ich lieber die CPU heiß laufen als meinen Kopf...
Jörg W. schrieb:> Armin G. schrieb:>> über eine individuell eingestellte Größe>> Naja, das klingt jetzt fast so, als könnte man sich diese beliebig> wählen.>
Hallo Jörg W.!
Als Anwendungsentwickler wird man dazu sicherlich ehr selten die
Gelegenheit haben, da gebe ich dir völlig Recht. In 16bit Emulationen,
welche die LocalDescriptorTable mit 64kByte Segmentgrößen verwenden,
wäre das zumindest noch denkbar.
Doch mitunter möchte man vielleicht auch mal an einem Kernel
(Linux/Minix/FORTH etc.) z.B. im Bereich 'Embedded' im Zusammenhang mit
Paging herum spielen. Da spielen dann die Einträge in Descriptor-Tables
(z.B. Intel GDT/LDT im Atom-Prozessor) schon noch eine Rolle. Und die
können auch individuell sehr unterschiedlich sein, sprich es können je
nach Prozessorarchitektur unterschiedliche Pagegrößen verwendet werden,
aber ebenso auch gemischte Page-Größen existieren ( Intel - Segment
Descriptor: GranularityBit[1], SegmentLimit[20] ). Ein Mix aus
4kByte(app) und 4MByte (kernel) Pages ist durchaus denkbar, u.U. sogar
sinnvoll.
> Selbst, wenn es keine feste Größe ist, sind es nur einige wenige> verschiedene Werte, die die Pagesize annehmen kann, und Rolfs> wesentliches Argument (dass man nur seitenweise überhaupt auslagern> kann und nicht ein paar einzelne ungenutzte Bytes) bleibt davon> völlig unberührt.
Naja, ich vermute mal, wenn man Annahmen über vorgegebene Pagegrößen
mit in die eigenen Überlegungen mit einbezieht, und diese sich jedoch
auch sehr deutlich von den 'vermuteten' Werten unterscheiden können,
dann hat das mitunter schon auch noch Einfluss auf das
Resourcenmanagement, sowie auch Performanceaspekte. Zudem ist ohne
Wissen um die tatsächliche PAGESIZE nicht möglich korrektes
Address-Alignment für Speicherallokationen vorzunehmen. Die Abfrage der
PAGESIZE kann also durchaus noch sinnvoll sein, besonders im Zusammheng
mit ObjectPooling / MemoryPooling.
Allokationen von z.B. 48kByte Buffern für eine FFT liegen in einem
System mit 64kByte PAGESIZE eben nur bei der ersten Allokation
(alignment vorausgesetzt) in ein und derselben Page, die nächste
Allokation jedoch liegt ein solcher Buffer ohne Alignment nun
überlappend über zwei unterschiedlichen Pages verteilt. Während in einem
System mit 4kByte PAGESIZE jede 48kByte Allokationen mehrere Pages
überspannt (alignment vorausgesetzt).
In der Deallokation oder dem, was tatsächlich ausgelagert wird/werden
kann, sind die Verhaltensweisen im Ergebnis nun doch sehr
unterschiedlich. Pages, welche durch Objekte mehrfach überspannt werden,
können bereits bei regelmäßig erfolgenden Zugriffen auf einen nur
kleinen Teil dieser Objekte, entsprechend gar nicht mehr ausgelagert
werden ('spanning'-Effekt).
Für Applikationen mit exzessivem Speicherverbrauch kann das durchaus
Probleme verursachen; da kommt dann u.U. auch mal ein sicherlich
heutzutage ehr selten zu sehender OOM-Error aufge-pop-pt.
Darüber hinaus können Objekte, welche bestimmte Alignments überspannen,
Penalities verursachen.
Beispiel: Ein 'double', welches das Alignment einer Cachzeile
überspannt, muss durch den Prozessor in mehr als einem einzelnen Zugriff
auf den Prozessorcache eingelesen werden.
- Anm.: (eigentlich ein blödes Beispiel, denn ein solcher double ist
ohnehin schon von Haus aus miss-aligned, nur eben zusätzlich auch noch
über die Grenzen einer cacheline hinweg)
An dieser Stelle wird deutlich, daß die PAGESIZE gemeinsam mit
Alignments für die Performance aufgrund der technischen Gegebenheiten
eine durchaus spürbare Rolle spielen können, auch wenn 'moderne'
Programmiersprachen mitunter anderes suggerieren. Denn auch wenn ein
einzelnes 'miss-aligned object' kaum Unterschiede ausmacht, in der Masse
tun sie das sehr wohl; mitunter sogar sehr deutlich.
Deshalb war es zumindest 'zu meiner Zeit' noch üblich, bei
Speicherintensiven Anwendungen zum einen die tatsächliche PAGESIZE
abzufragen, sowie u.U. auch ein Address-Alignment auf PAGESIZE-Boundary
bei der Allokation vieler Speicherobjekte vorzunehmen, sprich Pooling zu
betreiben, sowie auch Objekte wenn möglich auf technisch/Architektur
bedingte Alignments auszurichten (z.B. via '#pragma pack(...)').
Im Einzelfall konnte das auch bedeuten, Objekt-Pools in Vektoren von
Datentypen zu zerhackstückeln, um auf diesen Vektoren ohne
miss-aligments möglichst performant rechnen zu können.
SSE2 hat sogar mal für einige Operationen explizit verlangt, daß die
Daten sich an 128bit-boundaries ausrichten. Ob das wohl immer noch so
ist?
Bei der Gelegenheit: Ich bin ich mir gar nicht so sicher, ob nicht
PAGESIZE und die Aligments versus Data-Packing doch viel wichtigere
Aspekte sind, als eine mögliche Speicherfragmentierung insgesamt als
Problematik darstellt.
Ich persönlich würde bei den zunehmend größer werden
Arbeitsspeichergrößen (typisch 8-64GByte RAM für Desktop-PCs) jedenfalls
die Gefahr der Speicherfragmentierung ehr lax betrachten, mir jedoch
durchaus Gedanken über die Alignments im Zusammenhang mit PAGESIZE,
CacheLineSize, etc. machen. Denn das hat auch bei tollsten
Prozessorgeschwindigkeiten immer noch einen erheblichen Einfluss auf
das, was man aus dem Rechner an Leistung auch tatsächlich heraus
gequetscht bekommt.
Doch ich komme eben ursprünglich aus dem Bereich HPC; deshalb erhebe
ich da vorsorglich lieber keinen Anspruch auf allgemein gültige
Richtigkeit. Viele Anwendungen kommen sicherlich bereits mit alle dem
gut zurecht, was moderne Programmiersprachen und deren Compiler von Haus
aus anbieten, ohne sich um irgendwelche Details sorgen zu müssen.
Mit besten Grüßen aus Berlin!
Vlad T. schrieb:> Ich hätte erwartet, dass dies die Speicherbeschaffung einfach an das OS> weiterdelegiert.
So war das mal beim Microsoft C-Compiler und Windows 3.1. Nach ein paar
Tausend mallocs war dann Ende im Gelände: Out of Handles. Wir mußten
unseren eigenen Malloc schreiben.
Die nächste Compilerversion brachte dann ihr eigenes Speichermanagement
mit, was auch erheblich flotter war als das von Windows.