Warum Linux eine Auslagerungsdatei verwendet, Teil 2

Der erste Teil eines kleinen "Abzockens" über das Subsystem des virtuellen Speichers, die Verbindung von mmap-Mechanismen, gemeinsam genutzten Bibliotheken und Caches, löste eine so hitzige Diskussion aus, dass ich nicht widerstehen konnte, in der Praxis weiter zu forschen.



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.



All Articles