Anwendungsperformance

[ARCHIVIERT] Zuletzt aktualisiert von Joe Schaefer auf Fr., 26 Apr. 2024    Quelle
 

Viele Entwickler fallen in die Falle des Denkens, dass es bei der Leistungsoptimierung darum geht, jede Codezeile so effizient wie möglich zu machen.

Es ist eigentlich das Gegenteil. Sie beginnen mit den architektonischen Einschränkungen der Anwendung und verwenden sie, um einen Drilldown zum beobachteten “langsamsten” Teil des Programms durchzuführen. Die Implementierung dieses Teils führt alle anderen Performanceoptionen an, die Sie treffen müssen. Alles, was nicht so langsam ist wie dieser Teil, muss nicht weiter optimiert werden. Konzentrieren Sie sich stattdessen auf den menschlichen Ausdruck und die Einfachheit und Klarheit der Umsetzung, um nicht-expert Leser über die SSDLC

Sie können auf diesem Spielbuch iterieren, aber ich habe nie über 3 Iterationen in meiner beruflichen Karriere gehen müssen.

So gehen Sie voran und verwenden Sie eine elegante Programmiersprache wie Python3 oder Javascript/Skript, und lassen Sie sich von den Subject Matter Experts (SME) in der Open-Source-Welt mächtig machen C/C++

Selbst ein abhängigkeitsfreies Bash-Skript ist eine praktikable Lösung für viele grundlegende Aufgaben. Hier ist einer, den ich für Augmented Reality geschrieben habe Magischer Sprung Vor Jahren, um ein ungeschicktes OpenGrok Service mit etwas, das Multiprozessor-Parallelisierung nutzt mit xargs -Pund Support PCRE Suche mit einfachen Emacs/Vim

https://github.com/joesuf4/home/blob/wsl/bin/pffxg.sh

Dieses Skript ist eine Größenordnung schneller als die üblichen Verdächtigen auf GitHub, die alle in statischen, kompilierten Programmiersprachen geschrieben wurden. Aber durch die Identifizierung des genauen Engpasses in Basch (Schleifen mit hohem Volumen) fork+exec Aufrufe in der Mitte) und xargs Stattdessen erhalten Sie ein Skript, das viel wie dieses aussieht, wobei der Kernalgorithmus in 10 Zeilen von Shell

Es verwendet auch die Open-Source-Community von SME auf intelligente Weise, anstatt wie die anderen “gefilterten rekursiven Grep”-Implementierungen auf GitHub. Anstatt meine eigene (Threaded) Implementierung intern zu übernehmen und zu pflegen suchen, xargsund Grep, verwende ich nur die vorinstallierten ausführbaren Dateien, die andere SMEs über Jahrzehnte perfektioniert haben as-is. Ich muss ihre Implementierungen nicht beherrschen, sondern einfach wiederverwenden CLI

Um den gegenteiligen Tack zu sehen, bei dem alles intern durchgeführt wird, vollständig mikrooptimiert ist und dieses Skript immer noch nicht mit den Standardsuchoptionen übertroffen werden kann und kein Caching-System verfügbar ist, finden Sie hier ein schönes Beispiel https://github.com/BurntSushi/ripgrep

Nur um die erste #performance #benchmark von dieser Seite zu ziehen und sie von einer Spielzeugbeispielbaumgröße (Linux-Kernelquellen) auf einen heterogenen Baum zu skalieren, der 23GB: (beste Ausführung nach 3 Iterationen; SPRACHE=en_US.UTF-8

    % du -sh .
    23G .
    % time rg -uuniw '[A-Z]+_SUSPEND' | wc -l
    6259
    rg -uuniw '[A-Z]+_SUSPEND' 9.46s user 16.08s system 261% cpu 9.759 total
    wc -l 0.00s user 0.07s system 0% cpu 9.759 total
    % time pffxg.sh -- -wnE '[A-Z]+_SUSPEND' | wc -l
    5855
    pffxg.sh -- -wnE '[A-Z]+_SUSPEND' 16.66s user 2.68s system 429% cpu 4.501 total
    wc -l 0.00s user 0.00s system 0% cpu 4.501 total

Es ist ziemlich albern, etwas zu mikrooptimieren, das eng mit dem Status des Dateisystemcaches des Kernels für Ihre Suche verbunden ist. Die Variation der Performance-Timings wird von der Zugriffsgeschwindigkeit auf den Inhalt der Dateien dominiert, und es ist eine Größenordnung relevanter als jeder andere Faktor für die Endergebnisse. Auf einem NVMe hilft, aber nichts in diesem Raum schlägt RAM

Aus diesem Grund stabilisiert ein im Arbeitsspeicher komprimierter Cache für ein großes Korpus von Dateien die Performance-Timings. Es ist überraschend, dass niemand sonst dachte, dies sei wichtig genug, um zu unterstützen.

Nehmen Sie die zweite #performance #benchmark von dieser Seite, und skalieren Sie sie wie zuvor (gleich). 23GB

    % time rg -tc -uuuiwn '[A-Z]+_SUSPEND' | wc -l
    5629
    rg -tc -uuuiwn '[A-Z]+_SUSPEND' 3.51s user 1.71s system 1141% cpu 0.457 total
    wc -l 0.00s user 0.05s system 11% cpu 0.457 total
    % time LANG=C pffxg.sh --cache /tmp/pffxg-$USER --workers 32 --cc -- -wE '[A-Z]+_SUSPEND' | wc -l
    5628
    LANG=C pffxg.sh --cache /tmp/pffxg-$USER --workers 32 --cc -- -wE  3.14s user 0.88s system 1055% cpu 0.381 total
    wc -l 0.00s user 0.00s system 0% cpu 0.381 total

Abgestimmt pffxg.sh ist immer noch schneller, trotz aller Arbeiten, die in die mikrooptimierende Ripgrep für diese C

Wie ich dieses Skript verwendet habe mit AOSP sollte eine Repository Synchronisierung und eine nachfolgende pffxg.sh ZOP-compressed-cache Seed-to-SeedTmpfs jeden Morgen vor der Arbeit laufen (via Crontab), mit PFFXG_CACHE=... Set in meinem ~/.pffxg.conf Datei. So pffxg.sh Aufrufe, die ich während des Arbeitstages ausgeführt habe, verwenden den komprimierten Cache in Tmpfs

.25M LOC zwischen Ripgrep und ugrep. 632 LOC für pffxg.sh

Weil es so ein kleines Shell-Programm ist, pffxg.sh können Sie leistungsstarke Haken in seine Innenräume mit fast null Aufwand geben. Sogar Grep Befehl selbst ist anpassbar: Jeder Befehl, den Sie auf einem ausgewählten Korpus von Dateien ausführen müssen, der eine Liste von Dateinamen akzeptieren kann, die an das Ende seiner Argumente angehängt sind, ist ein faires Spiel. Hier ist eine “Gesamtpositionsanzahl in MiLOC

    % time find * -type f | xargs wc -l | awk '{ $2 == "total" {a+=$1} END {print a/1024**2}'
    28.451
    find * -type f 0.00s user 0.06s system 2% cpu 2.733 total
    xargs wc -l 0.53s user 1.02s system 54% cpu 2.853 total
    awk '$2 == "total" {a+=$1} END {print a/1024**2}' 0.23s user 0.59s system 28% cpu 2.853 total

    % time pffxg.sh --workers 8 --cmd wc --all -- -l | awk '{$2 == "total" {a+=$1} END {print a/1024**2}'
    28.4506
    pffxg.sh --workers 8 --cmd wc --all -- -l 0.92s user 0.66s system 826% cpu 0.192 total
    awk '$2 == "total" {a+=$1} END {print a/1024**2}' 0.02s user 0.00s system 11% cpu 0.192 total

Ripgrep

    % time rg -c \$ | awk -F : '{a+=$2} END {print a/1024**2}'
    28.4284
    rg -c \$ 2.12s user 2.19s system 276% cpu 1.564 total
    awk -F : '{a+=$2} END {print a/1024**2}' 0.58s user 0.45s system 66% cpu 1.564 total

Hier ist es beschränkt auf C

    % time pffxg.sh --workers 8 --cc --cmd wc -- -l | awk '$2 == "total" {a+=$1} END {print a/1024**2}'
    25.3935
    pffxg.sh --workers 8 --cc --cmd wc -- -l 0.76s user 0.54s system 734% cpu 0.177 total
    awk '$2 == "total" {a+=$1} END {print a/1024**2}' 0.02s user 0.00s system 9% cpu 0.177 total

und die Ripgrep

    % time rg -tc -c \$ | awk -F : '{a+=$2} END {print a/1024**2}'
    25.3844
    rg -tc -c \$ 3.49s user 1.54s system 441% cpu 1.140 total
    awk -F : '{a+=$2} END {print a/1024**2}' 0.38s user 0.38s system 66% cpu 1.140 total

Die tatsächliche Anwendungsleistung kommt von Balance, Flexibilität und funktionalen Programmierungstechniken; sie kommt nicht von der Fixierung auf zwingende Mikrooptimierungstaktiken in statischen, kompilierten Programmiersprachen, die aus der Balance- und Flexibilitätsperspektive ein Bär sind. Solche überholten, zwingenden Sprachen sind großartige Ziele für sehr spezifische Problemdomänen, sind aber für die systemweite Anwendungsleistung schrecklich.

pffxg.sh ist kein Produkt, und das ist kein Verkaufsargument dafür. Es ist ein Beispiel, um meinen Punkt auf eine sehr dramatische Weise zu veranschaulichen. Wenn Sie mit der langen Geschichte der gefilterten-rekursiven-grep-Lösungen auf GitHub vertraut sind, sind sie alle auf der Vorstellung, dass das Problem mit Andy Lesters Original Perl Implementierung Bestätigenwar, dass es in Perl. Das einzige wirkliche Problem aus Performance-Sicht war, dass die Perl wurde von Andy geschrieben, der anscheinend keine Ahnung von Systemleistungskonzepten hatte (wie z.B. suchen Parallelisierungsarbeit zu einem speziell entwickelten C Binär), sondern zielte auf faule Portabilität ab, indem er versuchte, den gesamten Code als Single-Thread-Pure zu erfassen Perl

Mögen tausend Blumen blühen, egal wie dumm sie scheinen!

 

   

Kommentare



Index

NonFunctional Tests