Bash-Skript; Optimierung der Verarbeitungsgeschwindigkeit

10

Ich habe mich gefragt, ob es allgemeine Richtlinien für die Optimierung von Bash-Skripten gibt.

  • Zum Beispiel ist es bequemer , Schleifen als Befehlszeilen zu schreiben, aber ist es auch schneller für das System zu verarbeiten? Beispiel:

    for i in a b c; do echo $i; done
    
    echo a
    echo b
    echo c
  • Manchmal präsentieren Menschen unterschiedliche Lösungen für das gleiche Problem. Zum Beispiel sed, cut, awkund echosind alle in der Lage Ziffern aus einer Zeichenfolge zu entfernen. Ich habe mich gefragt, ob Sie sagen können, dass je weniger Zifferncode vorhanden ist, desto schneller ist es, wenn Sie Folgendes verwenden:

    1. der gleiche Befehl, z

      STRING=abc.def
      echo ${STRING} | sed 's/.def//g'
      echo ${STRING} | sed '$s/....$//'
    2. verschiedene Befehle, z

      STRING=abc.def
      echo ${STRING} | cut -d . -f 1
      echo ${STRING} | sed 's/.def//g'
Vincent
quelle
2
In den meisten Fällen würde ich die Lesbarkeit der Geschwindigkeit vorziehen.
Bernhard
1
a) Ja, die Verwendung einer for-Schleife ist mit einem Overhead verbunden, aber es spielt keine Rolle. b) Versuchen Sie, externe Befehle zu minimieren und die Dinge nur innerhalb von bash zu ersetzen. Und wenn Sie Ihr Shell-Skript optimieren müssen, machen Sie etwas falsch und sollten die Verwendung einer Sprache mit besserer Profiler-Unterstützung in Betracht ziehen.
Ulrich Dangel
Reguläre Ausdrücke sind in Bezug auf die Leistung im Allgemeinen weniger effizient als jede andere Option. Wenn Sie also einen Weg sehen, etwas ohne einen zu tun (z. B. Ausschneiden), verwenden Sie diesen.
Dies

Antworten:

8

Shells führen keine Reorganisation des Codes durch, den sie erhalten, sondern werden nur eine Zeile nach der anderen interpretiert (nichts anderes macht in einem Befehlsinterpreter viel Sinn). Ein Großteil der von der Shell aufgewendeten Zeit wird für die lexikalische Analyse / Analyse / das Starten der aufgerufenen Programme aufgewendet.

Für einfache Operationen (wie die, die in den Beispielen am Ende der Frage Zeichenfolgen mischen) wäre ich überrascht, wenn die Zeit zum Laden der Programme keine winzigen Geschwindigkeitsunterschiede überschwemmt.

Die Moral der Geschichte lautet: Wenn Sie wirklich mehr Geschwindigkeit benötigen, sind Sie mit einer (halb) kompilierten Sprache wie Perl oder Python besser dran, die zunächst schneller ausgeführt werden kann und in der Sie viele der direkt genannten Operationen schreiben können Sie müssen keine externen Programme aufrufen und haben die Möglichkeit, externe Programme aufzurufen oder optimierte C-Module (oder was auch immer) aufzurufen, um einen Großteil der Arbeit zu erledigen. Dies ist der Grund, warum in Fedora die "System Administration Sugar" (im Wesentlichen GUIs) in Python geschrieben sind: Kann eine schöne GUI mit nicht allzu viel Aufwand hinzufügen, schnell genug für solche Anwendungen, direkten Zugriff auf Systemaufrufe haben. Wenn das nicht genug Geschwindigkeit ist, greifen Sie zu C ++ oder C.

Aber nicht dorthin gehen, es sei denn , man kann beweisen , dass der Performance - Gewinn wert ist der Verlust an Flexibilität und die Entwicklungszeit. Shell-Skripte sind nicht schlecht zu lesen, aber ich schaudere, wenn ich mich an einige Skripte erinnere, die zur Installation von Ultrix verwendet wurden, die ich einmal zu entschlüsseln versucht habe. Ich gab auf, zu viel "Shell-Skript-Optimierung" wurde angewendet.

vonbrand
quelle
1
1 , aber eine Menge Leute würde argumentieren , es ist eher ein sein Gewinn an Flexibilität und Entwicklungszeit so etwas wie Python oder Perl vs. Shell verwenden, keinen Verlust. Ich würde sagen, verwenden Sie ein Shell-Skript nur, wenn es erforderlich ist oder wenn Sie eine Vielzahl von Shell-spezifischen Befehlen ausführen.
Goldlöckchen
21

Die erste Regel für die Optimierung lautet: Nicht optimieren . Zuerst testen. Wenn die Tests zeigen, dass Ihr Programm zu langsam ist, suchen Sie nach möglichen Optimierungen.

Der einzige Weg, um sicher zu sein, ist ein Benchmarking für Ihren Anwendungsfall. Es gibt einige allgemeine Regeln, die jedoch nur für typische Datenmengen in typischen Anwendungen gelten.

Einige allgemeine Regeln, die unter bestimmten Umständen zutreffen können oder nicht:

  • Für die interne Verarbeitung in der Shell ist ATT ksh am schnellsten. Wenn Sie viele Zeichenfolgenmanipulationen durchführen, verwenden Sie ATT ksh. Dash kommt an zweiter Stelle; bash, pdksh und zsh bleiben zurück.
  • Wenn Sie eine Shell häufig aufrufen müssen, um jedes Mal eine sehr kurze Aufgabe auszuführen, gewinnt dash aufgrund der geringen Startzeit.
  • Das Starten eines externen Prozesses kostet Zeit, daher ist es schneller, eine Pipeline mit komplexen Teilen zu haben als eine Pipeline in einer Schleife.
  • echo $fooist langsamer als echo "$foo", da es ohne doppelte Anführungszeichen $fooin Wörter aufgeteilt wird und jedes Wort als Platzhaltermuster für Dateinamen interpretiert. Noch wichtiger ist, dass Spalt- und Globbing-Verhalten selten erwünscht ist. Denken Sie also daran, Variablen- und Befehlssubstitutionen immer in doppelte Anführungszeichen zu setzen: "$foo", "$(foo)".
  • Spezielle Werkzeuge überzeugen in der Regel Allzweckwerkzeuge. Zum Beispiel können Tools wie cutoder headmit denen emuliert werden sed, sind jedoch sedlangsamer und awksogar noch langsamer. Die Verarbeitung von Shell-Zeichenfolgen ist langsam, bei kurzen Zeichenfolgen jedoch besser als das Aufrufen eines externen Programms.
  • In fortgeschritteneren Sprachen wie Perl, Python und Ruby können Sie häufig schnellere Algorithmen schreiben, diese haben jedoch eine erheblich höhere Startzeit, sodass sie sich nur für die Leistung großer Datenmengen lohnen.
  • Zumindest unter Linux sind Pipes in der Regel schneller als temporäre Dateien.
  • Die meisten Anwendungen von Shell-Skripten beziehen sich auf E / A-gebundene Prozesse, sodass der CPU-Verbrauch keine Rolle spielt.

Es ist selten, dass die Leistung in Shell-Skripten ein Problem darstellt. Die obige Liste ist nur ein Hinweis. In den meisten Fällen ist es vollkommen in Ordnung, „langsame“ Methoden zu verwenden, da der Unterschied oft nur einen Bruchteil eines Prozent beträgt.

Normalerweise geht es bei einem Shell-Skript darum, etwas schnell zu erledigen. Sie müssen viel von der Optimierung profitieren, um zusätzliche Minuten für das Schreiben des Skripts zu rechtfertigen.

Gilles 'SO - hör auf böse zu sein'
quelle
2
Während pythonund rubysind definitiv langsamer zu starten, zumindest auf meinem System, perlist so schnell zu starten wie bashoder ksh. GNU awk ist deutlich langsamer als GNU sed, insbesondere in utf-8-Regionen, aber nicht für alle awks und seds. Das ksh93> dash> pdksh> zsh> bash ist nicht immer so eindeutig. Einige Muscheln sind in einigen Dingen besser als andere, und der Gewinner ist nicht immer der gleiche.
Stéphane Chazelas
2
Zu "Sie müssen viel von ... profitieren " : Wenn "Sie" die Benutzerbasis enthält, stimmt das. Mit Shell-Skripten in gängigen Linux-Paketen verschwenden Benutzer häufig mehrere Größenordnungen mehr Zeit, als der hastige Programmierer spart.
Agc
2

Wir werden hier unser Globbing-Beispiel oben erweitern, um einige Leistungsmerkmale des Shell-Skript-Interpreters zu veranschaulichen. Der Vergleich der bashund dashInterpreter für dieses Beispiel, in dem für jede der 30.000 Dateien ein Prozess erzeugt wird, zeigt, dass Dash die wcProzesse fast doppelt so schnell wie möglich verzweigen kannbash

bash-4.2$ time dash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.238s
user    0m0.309s
sys     0m0.815s


bash-4.2$ time bash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.422s
user    0m0.349s
sys     0m0.940s

Der Vergleich der Basisschleifengeschwindigkeit durch Nichtaufrufen der wcProzesse zeigt, dass die Schleifenschleife des Strichs fast sechsmal schneller ist!

$ time bash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m1.715s
user    0m1.459s
sys     0m0.252s



$ time dash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m0.375s
user    0m0.169s
sys     0m0.203s

Die Schleife ist in beiden Shells immer noch relativ langsam, wie zuvor gezeigt. Aus Gründen der Skalierbarkeit sollten wir versuchen, funktionalere Techniken zu verwenden, damit die Iteration in kompilierten Prozessen durchgeführt wird.

$ time find -type f -print0 | wc -l --files0-from=- | tail -n1
    30000 total
real    0m0.299s
user    0m0.072s
sys     0m0.221s

Das Obige ist bei weitem die effizienteste Lösung und verdeutlicht den Punkt, dass man im Shell-Skript so wenig wie möglich tun und nur versuchen sollte, die vorhandene Logik zu verbinden, die in den zahlreichen auf einem UNIX-System verfügbaren Dienstprogrammen verfügbar ist.

Aus allgemeinen Shell-Skriptfehlern von Pádraig Brady gestohlen .

Rahul Patil
quelle
1
Eine generische Regel: Die Bearbeitung von Dateideskriptoren kostet ebenfalls, reduzieren Sie also deren Anzahl. Anstatt es for i in *; do wc -l "$i">/dev/null; donebesser zu machen for i in *; do wc -l "$i"; done>/dev/null.
Manatwork
@manatwork es wird auch null Ausgabe von timecmd
Rahul Patil
@manatwork Gut ... jetzt Bitte geben Sie mir auch Ausgabe von ohne aufzurufen wc -l, überprüfen Sie, ob ich in der Post Ihre Ausgabe aktualisiert habe
Rahul Patil
Nun, die vorherigen Messungen wurden in einem kleineren Verzeichnis durchgeführt. Jetzt habe ich einen mit 30000 Dateien und wiederholten die Tests: pastebin.com/pCV6QKp2
Manatwork
Diese Benchmarks berücksichtigen nicht die unterschiedlichen Startzeiten jeder Shell. Benchmarks, die innerhalb jeder Shell durchgeführt werden, wären besser.
Agc