Warum ist das Öffnen einer Datei schneller als das Lesen von variablen Inhalten?

36

In einem bashSkript benötige ich verschiedene Werte aus /proc/Dateien. Bis jetzt habe ich Dutzende von Zeilen, die die Dateien direkt so abfassen:

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo

Um dies effizienter zu gestalten, habe ich den Dateiinhalt in einer Variablen gespeichert und Folgendes angegeben:

a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'

Anstatt die Datei mehrmals zu öffnen, sollte sie nur einmal geöffnet werden und der variable Inhalt, von dem ich angenommen habe, dass er schneller ist, wird jedoch langsamer:

bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real    0m0.803s
user    0m0.619s
sys     0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real    0m1.182s
user    0m1.425s
sys     0m0.506s

Gleiches gilt für dashund zsh. Ich habe den besonderen Status von /proc/Dateien als Grund vermutet , aber wenn ich den Inhalt /proc/meminfoin eine reguläre Datei kopiere und das Ergebnis als dasselbe bezeichne:

bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real    0m0.790s
user    0m0.608s
sys     0m0.227s

Durch die Verwendung eines Here-Strings zum Speichern der Pipe wird sie etwas schneller, aber immer noch nicht so schnell wie bei den Dateien:

bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real    0m0.977s
user    0m0.758s
sys     0m0.268s

Warum wird eine Datei schneller geöffnet als derselbe Inhalt aus einer Variablen gelesen?

Dessert
quelle
@ l0b0 Diese Annahme ist nicht fehlerhaft, die Frage zeigt, wie ich darauf gekommen bin und die Antworten erklären, warum dies der Fall ist. Ihre Bearbeitung führt nun dazu, dass die Antworten die Titelfrage nicht mehr beantworten: Sie sagen nicht, ob das der Fall ist.
Dessert
OK, klargestellt. Da die Überschrift in den allermeisten Fällen falsch war, wurden nur für bestimmte Speicherbereiche keine speziellen Dateien zugeordnet.
21.
@ l0b0 Nein, das ist es, was ich hier frage: "Ich habe den besonderen Status von /proc/Dateien als Grund vermutet , aber wenn ich den Inhalt /proc/meminfoeiner regulären Datei kopiere und benutze, sind die Ergebnisse die gleichen:" Es ist nichts Besonderes für /proc/Dateien, das Lesen von regulären Dateien ist auch schneller!
Dessert

Antworten:

47

Hier geht es nicht um das Öffnen einer Datei im Vergleich zu einer Variablen Inhalt zu lesen , sondern mehr über einen zusätzlichen Prozess Forking oder nicht.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfoForks Ein Prozess, der ausgeführt wird und grepgeöffnet wird /proc/meminfo(eine virtuelle Datei im Arbeitsspeicher, ohne dass Festplatten-E / A betroffen sind), liest sie und stimmt mit dem regulären Ausdruck überein.

Der teuerste Teil dabei ist, den Prozess zu forken und das grep-Dienstprogramm und seine Bibliotheksabhängigkeiten zu laden, die dynamische Verknüpfung durchzuführen, die Gebietsschemadatenbank zu öffnen und Dutzende von Dateien auf der Festplatte (aber wahrscheinlich im Arbeitsspeicher zwischengespeichert).

Der Teil über das Lesen /proc/meminfoist im Vergleich unbedeutend, der Kernel benötigt wenig Zeit, um die darin enthaltenen Informationen zu generieren, und grepbenötigt wenig Zeit, um sie zu lesen.

Wenn Sie strace -cdamit arbeiten, werden Sie feststellen, dass der eine open()und der andere read()Systemaufruf, der zum Lesen verwendet /proc/meminfowird, Erdnüsse ist, im Vergleich zu allem, was grepzum Starten strace -cerforderlich ist ( ohne Gabelung).

Im:

a=$(</proc/meminfo)

In den meisten Shells, die diesen $(<...)ksh-Operator unterstützen, öffnet die Shell nur die Datei und liest ihren Inhalt (und entfernt die nachfolgenden Zeilenumbrüche). bashist anders und viel weniger effizient, da es einen Prozess zum Lesen auffordert und die Daten über eine Pipe an das übergeordnete Element weiterleitet. Aber hier ist es einmal gemacht, also spielt es keine Rolle.

Im:

printf '%s\n' "$a" | grep '^MemFree'

Die Shell muss zwei Prozesse erzeugen , die gleichzeitig ablaufen, aber über eine Pipe miteinander interagieren. Das Erstellen, Abreißen und Schreiben und Lesen von Pfeifen ist mit geringen Kosten verbunden. Die weitaus höheren Kosten entstehen durch einen zusätzlichen Prozess. Die Planung der Prozesse hat ebenfalls einen Einfluss.

Möglicherweise können Sie feststellen, dass die Verwendung des <<<Operators zsh den Vorgang etwas beschleunigt:

grep '^MemFree' <<< "$a"

In zsh und bash geschieht dies durch Schreiben des Inhalts $ain eine temporäre Datei. Dies ist kostengünstiger als das Erstellen eines zusätzlichen Prozesses, bietet jedoch wahrscheinlich keinen Gewinn im Vergleich zum direkten Abrufen der Daten /proc/meminfo. Das ist immer noch weniger effizient als Ihr Ansatz, der /proc/meminfoauf die Festplatte kopiert , da das Schreiben der temporären Datei bei jeder Iteration erfolgt.

dashUnterstützt keine Here-Strings, aber seine Heredocs werden mit einer Pipe implementiert, die keinen zusätzlichen Prozess beinhaltet. Im:

 grep '^MemFree' << EOF
 $a
 EOF

Die Shell erzeugt eine Pipe, gabelt einen Prozess. Das untergeordnete Element wird grepmit der Standardeingabe als Leseende der Pipe ausgeführt, und das übergeordnete Element schreibt den Inhalt am anderen Ende der Pipe.

Das Pipehandling und die Prozesssynchronisation sind jedoch wahrscheinlich immer noch teurer, als nur die Daten direkt abzurufen /proc/meminfo.

Der Inhalt von /proc/meminfoist kurz und braucht nicht viel Zeit, um zu produzieren. Wenn Sie einige CPU-Zyklen einsparen möchten, möchten Sie die teuren Teile entfernen: Prozesse forken und externe Befehle ausführen.

Mögen:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

Vermeiden Sie bashjedoch, dass die Musterübereinstimmung sehr unzureichend ist. Mit zsh -o extendedglobkönnen Sie es verkürzen auf:

memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

Beachten Sie, dass dies ^in vielen Shells besonders ist (Bourne, fish, rc, es und zsh mindestens mit der Option extendedglob). Ich würde empfehlen, es zu zitieren. Beachten Sie auch, dass echonicht zur Ausgabe beliebiger Daten verwendet werden kann (daher meine Verwendung von printfoben).

Stéphane Chazelas
quelle
4
In dem Fall, in dem printfSie sagen, dass die Shell zwei Prozesse printferzeugen muss , aber keine eingebaute Shell ist?
David Conrad
6
@DavidConrad Es ist, aber die meisten Schalen nicht versuchen , die Pipeline zu analysieren , für welche Teile es könnte im laufenden Prozess ausgeführt werden . Es gabelt sich einfach und lässt die Kinder es herausfinden. In diesem Fall wird der übergeordnete Prozess zweimal gegabelt. das Kind für die linke Seite sieht dann ein eingebautes und führt es aus; das kind für die rechte seite sieht grepund führt aus.
Chepner
1
@DavidConrad, die Pipe ist ein IPC-Mechanismus, daher müssen die beiden Seiten auf jeden Fall in unterschiedlichen Prozessen ausgeführt werden. Während in A | B, gibt es einige Shells wie AT & T ksh oder zsh, die Bim aktuellen Shell-Prozess ausgeführt werden, wenn es ein eingebauter oder zusammengesetzter Befehl oder ein Funktionsbefehl ist, ich kenne keinen, der Aim aktuellen Prozess ausgeführt wird. Wenn überhaupt, müssten sie SIGPIPE auf eine komplexe Art und Weise behandeln, als ob Asie im untergeordneten Prozess ausgeführt würden, und ohne die Shell zu beenden, damit das Verhalten beim Bvorzeitigen Beenden nicht zu überraschend ist . Es ist viel einfacher, Bim übergeordneten Prozess auszuführen .
Stéphane Chazelas
Bash unterstützt<<<
D. Ben Knoble
1
@ D.BenKnoble, ich wollte nicht implizieren, bashnicht unterstützt <<<, nur, dass der Operator kam zshwie $(<...)kam von ksh.
Stéphane Chazelas
6

In Ihrem ersten Fall verwenden Sie einfach das grep-Dienstprogramm und suchen etwas aus einer Datei /proc/meminfo. Es /procist ein virtuelles Dateisystem, sodass sich die /proc/meminfoDatei im Speicher befindet und nur sehr wenig Zeit zum Abrufen des Inhalts benötigt wird.

Im zweiten Fall erstellen Sie jedoch eine Pipe und übergeben die Ausgabe des ersten Befehls mithilfe dieser Pipe an den zweiten Befehl. Dies ist sehr kostspielig.

Der Unterschied liegt an /proc(weil es sich im Speicher befindet) und an der Pipe (siehe Beispiel unten):

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s
Prvt_Yadav
quelle
1

In beiden Fällen rufen Sie einen externen Befehl auf (grep). Für den externen Anruf ist eine Unterschale erforderlich. Das Gabeln dieser Schale ist die fundamentale Ursache für die Verzögerung. Beide Fälle sind ähnlich, also: eine ähnliche Verzögerung.

Wenn Sie die externe Datei nur einmal lesen und (von einer Variablen) mehrmals verwenden möchten, verlassen Sie die Shell nicht:

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

Das dauert nur etwa 0,1 Sekunden anstatt der vollen 1 Sekunde für den grep-Aufruf.

Isaac
quelle