Deshalb werden wir heute ... Eine winzige Laborarbeit. In Form eines winzigen C-Programms, das wir schreiben, kompilieren und in Aktion testen - mit und ohne Tausch.
Das Programm macht eine sehr einfache Sache - es fordert einen großen Teil des Speichers an, greift darauf zu und arbeitet aktiv damit. Um nicht unter dem Laden von Bibliotheken zu leiden, erstellen wir einfach eine große Datei, die wie beim Laden gemeinsam genutzter Bibliotheken in den Speicher abgebildet wird.
Und wir emulieren einfach den Aufruf des Codes aus dieser "Bibliothek", indem wir aus einer solchen mmap-Datei lesen.
Das Programm führt mehrere Iterationen durch und greift bei jeder Iteration gleichzeitig auf den „Code“ und einen der Abschnitte eines großen Datensegments zu.
Und um keinen unnötigen Code zu schreiben, definieren wir zwei Konstanten, die die Größe des "Codesegments" und die Gesamtgröße des RAM bestimmen:
- MEM_GBYTES - Die Größe des Arbeitsspeichers für den Test
- LIB_GBYTES - "Code" -Größe
Die Menge an "Daten", die wir haben, ist geringer als die Menge an physischem Speicher:
- DATA_GBYTES = MEM_GBYTES - 2
Die Gesamtmenge an "Code" und "Daten" ist geringfügig größer als die Menge an physischem Speicher:
- DATA_GBYTES + LIB_GBYTES = MEM_GBYTES + 1
Für einen Test auf einem Laptop habe ich MEM_GBYTES = 16 genommen und die folgenden Eigenschaften erhalten:
- MEM_GBYTES = 16
- DATA_GBYTES = 14 - bedeutet, dass "Daten" 14 GB sind, dh "genügend Speicher"
- Swap-Größe = 16 GB
Programmtext
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define GB 1073741824l
#define MEM_SIZE 16
#define LIB_GBYTES 3
#define DATA_GBYTES (MEM_SIZE - 2)
long random_read(char * code_ptr, char * data_ptr, size_t size) {
long rbt = 0;
for (unsigned long i=0 ; i<size ; i+=4096) {
rbt += code_ptr[(8l * random() % size)] + data_ptr[i];
}
return rbt;
}
int main() {
size_t libsize = LIB_GBYTES * GB;
size_t datasize = DATA_GBYTES * GB;
int fd;
char * dataptr;
char * libptr;
srandom(256);
if ((fd = open("library.bin", O_RDONLY)) < 0) {
printf("Required library.bin of size %ld\n", libsize);
return 1;
}
if ((libptr = mmap(NULL, libsize,
PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
printf("Failed build libptr due %d\n", errno);
return 1;
}
if ((dataptr = mmap(NULL, datasize,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
-1, 0)) == MAP_FAILED) {
printf("Failed build dataptr due %d\n", errno);
return 1;
}
printf("Preparing test ...\n");
memset(dataptr, 0, datasize);
printf("Doing test ...\n");
unsigned long chunk_size = GB;
unsigned long chunk_count = (DATA_GBYTES - 3) * GB / chunk_size;
for (unsigned long chunk=0 ; chunk < chunk_count; chunk++) {
printf("Iteration %d of %d\n", 1 + chunk, chunk_count);
random_read(libptr, dataptr + (chunk * chunk_size), libsize);
}
return 0;
}
Test ohne Tausch
Deaktivieren Sie den Austausch, indem Sie vm.swappines = 0 angeben, und führen Sie den Test aus
$ time ./swapdemo Preparing test ... Killed real 0m6,279s user 0m0,459s sys 0m5,791s
Was ist passiert? Der Swappiness-Wert = 0 hat den Swap deaktiviert - anonyme Seiten werden nicht mehr hineingeschoben, dh die Daten befinden sich immer im Speicher. Das Problem ist, dass die verbleibenden 2 GB nicht ausreichten, damit Chrome und VSCode im Hintergrund ausgeführt werden, und der OOM-Killer das Testprogramm beendet hat. Gleichzeitig hat der Speichermangel den Chrome-Tab begraben, in dem ich diesen Artikel geschrieben habe. Und es hat mir nicht gefallen - auch wenn der Autosave funktioniert hat. Ich mag es nicht, wenn meine Daten begraben sind.
Tausch inbegriffen
Setzen Sie vm_swappines = 60 (Standard)
Führen Sie den Test aus:
$ time ./swapdemo Preparing test ... Doing test ... Iteration 1 of 11 Iteration 2 of 11 Iteration 3 of 11 Iteration 4 of 11 Iteration 5 of 11 Iteration 6 of 11 Iteration 7 of 11 Iteration 8 of 11 Iteration 9 of 11 Iteration 10 of 11 Iteration 11 of 11 real 1m55,291s user 0m2,692s sys 0m20,626s
Fragment oben:
Tasks: 298 total, 2 running, 296 sleeping, 0 stopped, 0 zombie %Cpu(s): 0,6 us, 3,1 sy, 0,0 ni, 85,7 id, 10,1 wa, 0,5 hi, 0,0 si, 0,0 st MiB Mem : 15670,0 total, 156,0 free, 577,5 used, 14936,5 buff/cache MiB Swap: 16384,0 total, 12292,5 free, 4091,5 used. 3079,1 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 10393 viking 20 0 17,0g 14,2g 14,2g D 17,3 93,0 0:18.78 swapdemo 136 root 20 0 0 0 0 S 9,6 0,0 4:35.68 kswapd0
Schlechtes, schlechtes Linux !!! Es verwendet fast 4 Gigabyte Swap, obwohl es 14 Gigabyte Cache und 3 Gigabyte zur Verfügung hat! Linux hat falsche Einstellungen! Schlechter Outlingo, schlechte alte Admins, sie verstehen nichts, sie sagten, sie sollen Swap aktivieren, und jetzt lassen sie das System tauschen und arbeiten schlecht für mich. Es ist notwendig, den Tausch zu deaktivieren, wie von viel jüngeren und vielversprechenden Internet-Experten empfohlen, da sie genau wissen, was zu tun ist!
Nun ... so sei es. Lassen Sie uns den Tausch auf Anraten der Experten so weit wie möglich ausschalten.
Test fast ohne Tausch
Wir setzen vm_swappines = 1
Dieser Wert führt dazu, dass der Austausch anonymer Seiten nur durchgeführt wird, wenn es keinen anderen Ausweg gibt.
Ich vertraue Chris Down, weil ich denke, dass er ein großartiger Ingenieur ist und weiß, was er sagt, wenn er erklärt, dass die Swap-Datei die Leistung des Systems verbessert. In der Erwartung, dass „etwas“ „schief gehen“ würde und das System möglicherweise furchtbar ineffizient funktioniert, habe ich mich im Voraus vergewissert und das Testprogramm ausgeführt und es mit einem Timer begrenzt, um zumindest die abnormale Beendigung zu erkennen.
Schauen wir uns zuerst die Top-Ausgabe an:
Tasks: 302 total, 1 running, 301 sleeping, 0 stopped, 0 zombie %Cpu(s): 0,2 us, 4,7 sy, 0,0 ni, 84,6 id, 10,0 wa, 0,4 hi, 0,0 si, 0,0 st MiB Mem : 15670,0 total, 162,8 free, 1077,0 used, 14430,2 buff/cache MiB Swap: 20480,0 total, 18164,6 free, 2315,4 used. 690,5 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6127 viking 20 0 17,0g 13,5g 13,5g D 20,2 87,9 0:10.24 swapdemo 136 root 20 0 0 0 0 S 17,2 0,0 2:15.50 kswapd0
Hurra?! Der Swap wird nur für 2,5 Gigabyte verwendet, was fast zweimal weniger ist als im Test mit aktiviertem Swap (und Swapiness = 60). Swap wird weniger verwendet. Es gibt auch weniger freien Speicher. Und wir können den Sieg wahrscheinlich sicher jungen Experten geben. Aber hier ist das Seltsame: Unser Programm konnte nicht einmal 1 (EINE!) Iteration in 2 (ZWEI!) Minuten durchführen:
$ { sleep 120 ; killall swapdemo ; } & [1] 6121 $ time ./swapdemo Preparing test … Doing test … Iteration 1 of 11 [1]+ Done { sleep 120; killall swapdemo; } Terminated real 1m58,791s user 0m0,871s sys 0m23,998s
Wir wiederholen - das Programm konnte nicht 1 Iteration in 2 Minuten abschließen, obwohl es im vorherigen Test 11 Iterationen in 2 Minuten durchgeführt hat - das heißt, wenn der Swap fast deaktiviert ist, läuft das Programm mehr als 10 (!) Mal langsamer.
Aber es gibt ein Plus - kein einziger Chrome-Tab wurde beschädigt. Und das ist gut.
Testen Sie mit vollständig deaktiviertem Swap
Aber vielleicht reicht es nicht aus, den Tausch nur durch Tausch zu "zerquetschen", und er sollte vollständig deaktiviert werden? Natürlich sollte diese Theorie auch getestet werden. Wir sind hierher gekommen, um Tests durchzuführen, oder was?
Dies ist der Idealfall:
- Wir haben keinen Tausch und alle unsere Daten werden im Speicher garantiert
- Der Tausch wird auch nicht versehentlich verwendet, da er nicht vorhanden ist
Und jetzt endet unser Test blitzschnell, die alten Leute gehen an den Ort, den sie verdienen, und wechseln die Patronen - den Weg für die jungen.
Leider ist das Ergebnis der Ausführung des Testprogramms ähnlich - es wurde nicht einmal eine Iteration abgeschlossen.
Top Ausgabe:
Tasks: 217 total, 1 running, 216 sleeping, 0 stopped, 0 zombie %Cpu(s): 0,0 us, 2,2 sy, 0,0 ni, 85,2 id, 12,6 wa, 0,0 hi, 0,0 si, 0,0 st MiB Mem : 15670,0 total, 175,2 free, 331,6 used, 15163,2 buff/cache MiB Swap: 0,0 total, 0,0 free, 0,0 used. 711,2 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 136 root 20 0 0 0 0 S 12,5 0,0 3:22.56 kswapd0 7430 viking 20 0 17,0g 14,5g 14,5g D 6,2 94,8 0:14.94 swapdemo
Warum passiert das
Die Erklärung ist sehr einfach - das „Codesegment“, das wir über mmap (libptr) verbinden, befindet sich im Cache. Wenn wir also den Swap auf die eine oder andere Weise verbieten (oder fast verbieten), spielt es keine Rolle, wie - durch physisches Deaktivieren des Austauschs oder durch vm.swappines = 0 | 1 - immer das gleiche Szenario endet - das Löschen der mmap-Datei aus dem Cache und dann von der Festplatte laden. Und Bibliotheken werden genau über mmap geladen. Um dies zu überprüfen, müssen Sie nur ls -l / proc // map_files ausführen:
$ ls -l /proc/8253/map_files/ | head -n 10 total 0 lr-------- 1 viking viking 64 7 12:58 556799983000-55679998e000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 55679998e000-5567999af000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 5567999af000-5567999bf000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 5567999c0000-5567999c4000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 5567999c4000-5567999c5000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 7fb22a033000-7fb22a062000 -> /usr/share/glib-2.0/schemas/gschemas.compiled lr-------- 1 viking viking 64 7 12:58 7fb22b064000-7fb238594000 -> /usr/lib/locale/locale-archive lr-------- 1 viking viking 64 7 12:58 7fb238594000-7fb2385a7000 -> /usr/lib64/gvfs/libgvfscommon.so lr-------- 1 viking viking 64 7 12:58 7fb2385a7000-7fb2385c3000 -> /usr/lib64/gvfs/libgvfscommon.so
Und wie wir im ersten Teil des Artikels betrachtet haben, wählt das System unter Bedingungen eines tatsächlichen Speichermangels beim Deaktivieren des Austauschs anonymer Seiten die einzige Option, die vom Eigentümer übrig geblieben ist, der den Austausch deaktiviert hat. Mit dieser Option werden leere Seiten zurückgefordert (freigegeben), die mit den Daten von mit mmap geladenen Bibliotheken belegt sind.
Fazit
Die aktive Nutzung der Softwareverteilungsmethode „Ich nehme alles mit“ (Flatpak, Snap, Docker-Image) führt dazu, dass die Menge an Code, die über mmap verbunden ist, erheblich zunimmt.
Dies kann dazu führen, dass die Verwendung von "extremen Optimierungen" im Zusammenhang mit dem Festlegen / Deaktivieren des Austauschs zu völlig unerwarteten Effekten führen kann, da eine Auslagerungsdatei ein Mechanismus zur Optimierung des virtuellen Speichersubsystems unter Bedingungen des Speicherdrucks ist und der verfügbare Speicher dies ist Ganz und gar nicht "unbenutzter Speicher", sondern die Summe aus Cache und freiem Speicher.
Durch Deaktivieren der Auslagerungsdatei entfernen Sie nicht "die falsche Option", sondern "lassen keine Optionen".
Sie sollten bei der Interpretation der Prozessspeicherverbrauchsdaten - VSS und RSS - sehr vorsichtig sein. Sie repräsentieren "aktuellen Zustand" und nicht "optimalen Zustand".
Wenn Sie nicht möchten, dass das System den Swap verwendet, fügen Sie Speicher hinzu, deaktivieren Sie den Swap jedoch nicht . Wenn Sie den Swap auf Schwellenwerten deaktivieren, wird sich die Situation erheblich verschlechtern, als wenn das System ein wenig getauscht worden wäre.
PS: In den Diskussionen werden regelmäßig Fragen gestellt, "aber wenn Sie die Speicherkomprimierung über zram aktivieren ...". Ich wurde neugierig und führte die entsprechenden Tests durch: Wenn Sie zram und swap aktivieren, wie dies in Fedora standardmäßig der Fall ist, beschleunigt sich die Laufzeit auf ca. 1 Minute.
Der Grund dafür ist jedoch, dass Seiten mit Nullen sehr gut komprimiert sind, sodass die Daten nicht ausgetauscht werden, sondern in komprimierter Form im RAM gespeichert werden. Wenn Sie ein Datensegment mit zufälligen, schlecht komprimierbaren Daten füllen, wird das Bild weniger spektakulär und die Testlaufzeit erhöht sich erneut auf 2 Minuten, was vergleichbar (und sogar etwas schlechter) ist als das einer "ehrlichen" Auslagerungsdatei.