it-swarm.com.de

Warum ist das Öffnen einer Datei schneller als das Lesen variabler Inhalte?

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

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

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

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

Anstatt die Datei mehrmals zu öffnen, sollte sie nur einmal geöffnet und der variable Inhalt erfasst werden, von dem ich angenommen habe, dass er schneller ist - aber tatsächlich ist er 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 dash und zsh. Ich habe den besonderen Status von /proc/ - Dateien als Grund vermutet, aber wenn ich den Inhalt von /proc/meminfo In eine reguläre Datei kopiere und verwende, sind die Ergebnisse dieselben:

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

Wenn Sie hier eine Zeichenfolge zum Speichern der Pipe verwenden, 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 ist das Öffnen einer Datei schneller als das Lesen desselben Inhalts aus einer Variablen?

36
dessert

Hier geht es nicht darum, eine Datei zu öffnen oder den Inhalt einer Variablen zu lesen , sondern mehr über das Verzweigen eines zusätzlichen Prozesses oder nicht.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo Gabelt einen Prozess, der grep ausführt, der /proc/meminfo Öffnet (eine virtuelle Datei im Speicher, keine Festplatten-E/A beteiligt), liest sie und stimmt mit dem regulären Ausdruck überein.

Der teuerste Teil dabei ist das Verzweigen des Prozesses und das Laden des Dienstprogramms grep und seiner Bibliotheksabhängigkeiten, das dynamische Verknüpfen, das Öffnen der Gebietsschemadatenbank und Dutzende von Dateien, die sich auf der Festplatte befinden (aber wahrscheinlich im Speicher zwischengespeichert sind).

Der Teil über das Lesen von /proc/meminfo Ist im Vergleich unbedeutend, der Kernel benötigt wenig Zeit, um die Informationen dort zu generieren, und grep benötigt wenig Zeit, um sie zu lesen.

Wenn Sie strace -c Darauf ausführen, sehen Sie, dass die Systemaufrufe open() und read(), die zum Lesen von /proc/meminfo Verwendet werden, im Vergleich zu allem Erdnüsse sind sonst startet grep (strace -c zählt das Gabeln nicht).

Im:

a=$(</proc/meminfo)

In den meisten Shells, die diesen Operator $(<...) ksh unterstützen, öffnet die Shell nur die Datei und liest ihren Inhalt (und entfernt die nachfolgenden Zeilenumbrüche). bash ist anders und viel weniger effizient, da es einen Prozess zum Lesen anregt 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 ausgeführt werden, aber über eine Pipe miteinander interagieren. Das Erstellen, Abreißen und Schreiben und Lesen von Pfeifen hat nur geringe Kosten. Die viel höheren Kosten sind das Laichen eines zusätzlichen Prozesses. Die Planung der Prozesse hat ebenfalls einige Auswirkungen.

Möglicherweise können Sie mit dem Operator zsh <<< Etwas schneller arbeiten:

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

In zsh und bash wird dazu der Inhalt von $a In eine temporäre Datei geschrieben. Dies ist kostengünstiger als das Erstellen eines zusätzlichen Prozesses, bringt Ihnen jedoch wahrscheinlich keinen Gewinn im Vergleich zum direkten Abrufen der Daten /proc/meminfo. Das ist immer noch weniger effizient als Ihr Ansatz, /proc/meminfo Auf die Festplatte zu kopieren, da das Schreiben der temporären Datei bei jeder Iteration erfolgt.

dash unterstützt keine Here-Strings, aber seine Heredocs werden mit einer Pipe implementiert, bei der kein zusätzlicher Prozess erzeugt wird. Im:

 grep '^MemFree' << EOF
 $a
 EOF

Die Shell erstellt ein Rohr und gibt einen Prozess vor. Das untergeordnete Element führt grep mit seinem Standard als Leseende der Pipe aus, und das übergeordnete Element schreibt den Inhalt am anderen Ende der Pipe.

Diese Rohrhandhabung und Prozesssynchronisation ist jedoch wahrscheinlich immer noch teurer als nur die Daten direkt abzurufen /proc/meminfo.

Der Inhalt von /proc/meminfo Ist kurz und die Erstellung dauert nicht lange. Wenn Sie einige CPU-Zyklen speichern möchten, möchten Sie die teuren Teile entfernen: Forking-Prozesse und Ausführen externer Befehle.

Mögen:

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

Vermeiden Sie bash, dessen Musterübereinstimmung jedoch sehr unzulänglich ist. Mit zsh -o extendedglob Können Sie es verkürzen auf:

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

Beachten Sie, dass ^ In vielen Shells (Bourne, fish, rc, es und zsh mit mindestens der Extendedglob-Option) etwas Besonderes ist. Ich würde empfehlen, es zu zitieren. Beachten Sie auch, dass echo nicht zur Ausgabe beliebiger Daten verwendet werden kann (daher meine Verwendung von printf oben).

47

In Ihrem ersten Fall verwenden Sie nur das Dienstprogramm grep und suchen etwas aus der Datei /proc/meminfo, /proc ist ein virtuelles Dateisystem, also /proc/meminfo Datei befindet sich im Speicher und benötigt nur sehr wenig Zeit, um ihren Inhalt abzurufen.

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

Der Unterschied liegt an /proc (weil es sich im Speicher befindet) und Pipe, siehe das folgende Beispiel:

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
6
Prvt_Yadav

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

Wenn Sie die externe Datei nur einmal lesen und (aus 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

Dies dauert nur etwa 0,1 Sekunden anstelle der vollen 1 Sekunde für den Grep-Anruf.

1
Isaac