Forum: PC-Programmierung Speicherfragmentierung kein Thema mehr?


von lalala (Gast)


Lesenswert?

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.)?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

lalala schrieb:
> verschiedene Memorypools einrichten

Sowas machen ordentliche malloc()-Implementierungen von sich aus.

von Peter II (Gast)


Lesenswert?

Jörg W. schrieb:
> Sowas machen ordentliche malloc()-Implementierungen von sich aus.

spätestens beim realloc wird es aber schwer.

von lalala (Gast)


Lesenswert?

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?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von lalala (Gast)


Lesenswert?

Jörg W. schrieb:
> Sourcecode ansehen.

Hä? Welchen, den z.B. von Windows oder den von Visual Studio?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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. ;-)

von Peter II (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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).

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Amateur (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Vlad T. (vlad_tepesch)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Amateur (Gast)


Lesenswert?

@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.

von Peter II (Gast)


Lesenswert?

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.

von Vlad T. (vlad_tepesch)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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:
1
$ pwd
2
/usr/src/contrib/jemalloc/src
3
$ wc -l *.c
4
    2365 arena.c
5
       2 atomic.c
6
     142 base.c
7
      90 bitmap.c
8
     395 chunk.c
9
     197 chunk_dss.c
10
     210 chunk_mmap.c
11
     563 ckh.c
12
    1673 ctl.c
13
      39 extent.c
14
       2 hash.c
15
     313 huge.c
16
    1873 jemalloc.c
17
       2 mb.c
18
     160 mutex.c
19
    1283 prof.c
20
     190 quarantine.c
21
      67 rtree.c
22
     549 stats.c
23
     476 tcache.c
24
     107 tsd.c
25
     657 util.c
26
   11355 total

von Vlad T. (vlad_tepesch)


Lesenswert?

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?

von Peter II (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Vlad T. (vlad_tepesch)


Lesenswert?

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?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Vlad T. (vlad_tepesch)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Vlad T. (vlad_tepesch)


Lesenswert?

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.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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. :}

: Bearbeitet durch Moderator
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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!)

von S. R. (svenska)


Lesenswert?

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.

von Pümpler (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von lalala (Gast)


Lesenswert?

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.

von tall (Gast)


Lesenswert?

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.

von Armin G. (Firma: Rentenvers. Bund (Frührentner)) (armin_g)


Lesenswert?

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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Sebastian S. (amateur)


Lesenswert?

>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.

von Klaus (Gast)


Lesenswert?

Pümpler schrieb:
> Interessant; ich habe da vorher noch nie reingesehen, muss ich zugeben.

Da gibt es aber kein malloc ... Unter C:\Program Files (x86)\Windows 
Kits\10 zwar malloc.cpp, aber das hat hiermit zu tun:
https://blogs.msdn.microsoft.com/vcblog/2015/03/03/introducing-the-universal-crt/

von Arc N. (arc)


Lesenswert?

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

: Bearbeitet durch User
von tictactoe (Gast)


Lesenswert?

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...

von Armin G. (Firma: Rentenvers. Bund (Frührentner)) (armin_g)


Lesenswert?

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!

: Bearbeitet durch User
von Markus L. (rollerblade)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.