Einführung von Prinzipien und Beispielen, die auf der Programmseite vielseitig in der Leistungsoptimierung sind und in jeder Sprache oder jedem Bereich wahrscheinlich kostengünstig sind.
Beachten Sie beim Optimieren und Entwickeln Ihres Programms die folgenden drei Prinzipien.
Die Optimierung und Optimierung der Systemarchitektur, der Hardwarekonfiguration usw. fällt nicht in den Geltungsbereich dieses Artikels. (Die Prinzipien 2 und 3 können angewendet werden, aber wir werden nicht so weit gehen.)
Wenn ich an verschiedenen Systementwicklungen beteiligt bin, habe ich das Gefühl, dass es überraschend wenige Menschen gibt, die sich der Leistung in Design und Entwicklung bewusst sind. In Anbetracht des Zwecks und der Leistungsanforderungen des Entwicklungsziels wird die Entwicklung ohne Zwischenfälle fortgesetzt, auch wenn es sich um einen Teil handelt, der eindeutig ein großes Problem bei der Leistungsüberprüfung usw. zu sein scheint.
Natürlich ist der Ausdruck "Halbwertsoptimierung ist schlecht und sollte später bei Bedarf angepasst werden" richtig. Wird von dieser Maxime jedoch nicht implizit erwartet, dass sie Hypothesen und Vorhersagen über einige Leistungsengpässe zum Zeitpunkt des Entwurfs und der Codierung aufstellt? Aus diesem Grund denke ich, dass häufig der Satz "Erleichtern wir das spätere Stimmen, indem wir es zu einer Methode oder Modularisierung machen, bei der ein Tuning erforderlich ist" hinzugefügt wird.
Ohne diese Tatsache zu verstehen, fuhr ich mit Arbeiten wie Codierung und Komponententests fort, ohne Hypothesen und Vorhersagen über Leistungsengpässe zu machen. Als Systemtests durchgeführt wurden, traten schwerwiegende Leistungsengpässe auf und ich ging hin und her. Es ist nicht ungewöhnlich, dass es verschwindet. ** Es ist in Ordnung zu sagen, dass die Leistungsoptimierung bei Bedarf später durchgeführt werden sollte. Wenn Sie jedoch nicht über die erforderlichen Richtlinien (Hypothesen und Vorhersagen), Kenntnisse, Fähigkeiten usw. verfügen, handelt es sich nur um eine theoretische Theorie. .. Es ist nicht anders, als nur die Problembehandlung zu verschieben. ** ** **
NOTE: Wenn Sie etwas wie Intel DPDK (Entwicklungskit für die Verarbeitung von Netzwerkpaketen mit superschneller Geschwindigkeit) entwickeln möchten, müssen Sie natürlich alle Kenntnisse und Fähigkeiten für eine weitergehende Optimierung mobilisieren (z. B. Parallelisierung). , CPU usw. Affinität, Cache-Treffer, Cache-Zeilen, CPU-Architektur, Compiler-Optionen, Hardware-Offload usw.). Sofern es sich nicht um einen so speziellen Bereich handelt, gibt es andere Optimierungen mit höchster Priorität.
In diesem Artikel werden einige der Prinzipien der Leistungsoptimierung vorgestellt, die die folgenden Eigenschaften aufweisen, sowie Beispiele typischer Techniken.
Es gibt viel mehr spezialisierte und erweiterte Optimierungen, die sich auf Programmiersprachen, Architekturen, Hardware usw. spezialisiert haben, aber die allgemeineren Prinzipien sollten zuerst befolgt werden.
NOTE: Gelegentlich bin ich mit den Optionen zur Compileroptimierung, der Parameteroptimierung von Linux-Kerneln usw. sehr vertraut, kenne aber keine der in diesem Artikel behandelten grundlegenden Leistungsoptimierungen. Da solche Personen versuchen, das Problem durch Leistungsoptimierung in ihrer eigenen Spezialität zu lösen, neigen sie dazu, je nach Leistungsengpass eine äußerst ineffektive Leistungsoptimierung durchzuführen. Für Ingenieure, die mit der Umgebung nicht sehr vertraut sind, gibt es einen Bonus, der das Missverständnis verbreitet, dass "es nicht geholfen werden kann, weil es nicht so viel getan werden kann".
Hier sind drei Prinzipien, die ich für am wichtigsten halte. Die Wichtigkeit variiert je nach Situation, aber grundsätzlich wird empfohlen, die Prinzipien in der Reihenfolge von oben anzuwenden.
HINWEIS: Ich möchte mehr hinzufügen, aber wenn es zu viele gibt, ist es schwierig zu üben, daher habe ich mich auf drei beschränkt.
Eines der wichtigsten Dinge bei der Leistungsoptimierung ist die Reduzierung des Rechenaufwands bei der Programmverarbeitung. Wenn Sie den Rechenaufwand, die O-Notation usw. nicht kennen, lesen Sie bitte die folgenden Artikel.
[Für Anfänger] So ermitteln Sie den Rechenaufwand für ein Programm Zusammenfassung zur Berechnung der Berechnungsbetrag! ~ Woher kommt das Protokoll ~
Es ist ein gesunder Menschenverstand für diejenigen, die sich mit Informationssystemen und Informatik befassen, aber leider können nur wenige Menschen es praktizieren, selbst wenn sie es wissen. Egal, ob Sie ein Programm schreiben oder SQL für eine Datenbank schreiben, denken Sie daran, immer den Rechenaufwand zu berücksichtigen.
HINWEIS: Übrigens ist es im Fall von DB SQL verwirrend, da das Optimierungsprogramm den internen Ausführungsplan abhängig von der Anzahl der Datensätze in der DB-Tabelle zu diesem Zeitpunkt, statistischen Informationen usw. ändern kann. Aus diesem Grund kann SQL, das ursprünglich mit dem Berechnungsbetrag O (N) betrieben wurde, beispielsweise aufgrund der unzureichenden Genauigkeit der DB-Statistiken zu O (N ^ 2) werden.
Als grober Standard gilt: ** Wenn der Berechnungsbetrag O (N) oder mehr beträgt, achten Sie darauf, dass dies ein Hindernis für die Leistung darstellt, und wenn es O (N ^ 2) oder mehr ist, ist dies ein absolutes Hindernis für die Leistung. Es wird empfohlen, dies mit dem Bewusstsein von ** zu überprüfen.
Wenn beispielsweise die Anzahl der Berechnungen nach der Formel "3 * n" berechnet wird, beträgt der Berechnungsbetrag O (N). Sie können den Koeffizienten 3 ignorieren.
Es ist jedoch wichtig, diesen Koeffizienten zu kennen, wenn Sie die Leistung des Programms berücksichtigen. Wenn beispielsweise die Anzahl der Berechnungen für "10 * n" 10 Sekunden beträgt, kann die Verarbeitungszeit auf 1 Sekunde verbessert werden, wenn der Koeffiziententeil auf 1 gesetzt werden kann. Natürlich hängt es von den Anforderungen des Entwicklungsziels ab, aber ich denke, dass es genügend Fälle gibt, in denen die Verbesserung dieses Koeffiziententeils äußerst wichtig ist.
Die nachstehend beschriebenen Prinzipien 2 und 3 können auch verwendet werden, um diesen Koeffizientenanteil zu verringern.
In Anbetracht der vom Programm durchgeführten Verarbeitung und der erforderlichen Leistung (z. B. Verarbeitungszeit) kann es zu einer kostenintensiven Verarbeitung kommen, die ein Haupthindernis für die Erfüllung der erforderlichen Leistung darstellt. Es ist sehr wahrscheinlich, dass eine solche ** kostenintensive Verarbeitung viele Male durchgeführt wird. Erwägen Sie daher, alles auf einmal auszuführen (Stapelverarbeitung) **.
Es ist von Fall zu Fall zu entscheiden, ob es sich um einen kostenintensiven Prozess handelt. Bei den folgenden Prozessen handelt es sich jedoch wahrscheinlich um "kostenintensive Prozesse, die die erforderliche Leistung beeinträchtigen". Wir empfehlen daher, sie als Kandidaten für kostenintensive Prozesse zu betrachten.
Wie bereits erwähnt, ist es in einigen Situationen möglicherweise kein Problem, auch wenn es wahrscheinlich ein Hindernis ist. Betrachten Sie beispielsweise drei Muster, wenn "SQL INSERT (es dauert jedes Mal 1 ms)", der Steuerbegriff eines bestimmten Prozesses, ausgeführt wird.
1ms * 10 = 10ms
ist es äußerst unwahrscheinlich, dass es zu einem Leistungsengpass kommt. ** Kein Problem **
--SQL INSERT wird 10.000 Mal in dem Prozess ausgeführt, der innerhalb von 3 Sekunden ausgeführt werden muss1ms * 10000 = 10s
wird es zu einem schwerwiegenden Leistungsengpass. ** Problem **
--SQL INSERT wird 10.000 Mal in dem Prozess ausgeführt, der innerhalb von 60 Sekunden ausgeführt werden muss1ms * 10000 = 10s
ist es äußerst unwahrscheinlich, dass es zu einem Leistungsengpass kommt. ** Kein Problem **Wie Sie den obigen drei Mustern entnehmen können, hängt es von den Leistungsanforderungen und der Anzahl der Verarbeitungselemente ab, ob es zu einem Hindernis wird oder nicht. Aus diesem Grund ist es wichtig, eine grobe Schätzung des möglichen Hindernisses (Kandidat für eine kostenintensive Verarbeitung) vorzunehmen und grob zu bestimmen, ob ein echtes Problem vorliegt oder nicht **. ist. Wenn Sie nicht schließen können, dass kein Problem vorliegt, wird dieser Teil als Bestätigungspunkt angezeigt.
Sobald Sie den kostenintensiven Prozess identifiziert haben, müssen Sie überlegen, wie dieses Teil gestapelt werden soll. Im Allgemeinen weist die oben beschriebene kostenintensive Verarbeitung eine Form der kollektiven Ausführung auf, und in vielen Fällen kann die Verarbeitungszeit erheblich reduziert werden, wenn sie gemeinsam ausgeführt wird. Wenn Sie nicht über ein solches Mittel verfügen, muss der Angerufene möglicherweise ein Mittel implementieren, um alles zusammen zu erledigen.
NOTE: Skriptsprachen wie Python und Ruby sind viele- bis zehnmal langsamer als kompilierte Sprachen wie C / C ++ / Java (für JavaScript ist es aufgrund der bemerkenswerten Entwicklung der V8-Engine usw. etwas langsamer. Die Dinge sind anders). Abhängig vom Verarbeitungsinhalt und den Leistungsanforderungen kann sich die schrittweise Verarbeitung selbst als kostenintensive Verarbeitung herausstellen. Verwenden Sie in einem solchen Fall so weit wie möglich "Standardfunktionen und Bibliotheken, die für die Hochgeschwindigkeitsverarbeitung in C-Sprache usw. implementiert sind", um sie alle zusammen zu verarbeiten. Im Fall von Python wird die Erzeugung und Verarbeitung großer Arrays beispielsweise Bibliotheken wie Numpy so weit wie möglich überlassen.
—— Dutzende bis Hunderte von externen Befehlen ausführen
Wenn Sie beispielsweise ein Programm verarbeiten, wird es meiner Meinung nach häufig mithilfe von Threads und verschiedenen Verbindungen (z. B. HTTP, SQL) implementiert. Wenn Sie sie häufig verwenden, kann es überraschend hohe Kosten verursachen, jedes Mal eine neue zu erstellen und zu verwerfen. Für solche ** "hohen Kosten und häufig verwendeten`s, wenn es wiederverwendet werden kann, erwägen Sie einen Mechanismus für die Wiederverwendung **
Was teuer ist, hängt natürlich stark vom Zweck des Programms und den Leistungsanforderungen ab. Dies entspricht dem Prinzip 2. Wenn Sie beispielsweise eine HTTP-Anfrage nur einmal alle 10 Minuten senden, ist der Vorteil der Wiederverwendung einer HTTP-Verbindung gering. Wenn Sie sie jedoch zehn- bis hundertmal pro Sekunde senden, ist der Vorteil der Wiederverwendung einer HTTP-Verbindung groß. ist. Abhängig von den Verwendungs- und Leistungsanforderungen des Programms kann es sehr kostspielig sein, Speicher mit malloc zuzuweisen und ihn bei jeder Verwendung des Speichers kostenlos freizugeben. In diesem Fall müssen Sie den Speicherbereich selbst neu zuweisen. Der Vorteil der Einführung eines zu verwendenden Mechanismus ist ebenfalls groß.
Die typischen Wiederverwendungsziele sind unten aufgeführt.
--Verbindungen wie HTTP und DB
Das Prinzip der Optimierung gilt, wenn Sie Folgendes tun:
Überprüfen Sie beim Codieren Folgendes. Wenn es anwendbare Prinzipien gibt, kann dies ein Ort sein, der später optimiert werden muss.
―― Ob jedes Prinzip erfüllt ist oder nicht ――Die folgenden Maßnahmen werden für die Trefferpunkte ergriffen. --TODO Kommentar, Protokolleinbettung
Übrigens, je feiner die Reichweite ist, desto mehr Zeit und Mühe wird benötigt. Es ist also in Ordnung, die Reichweite von "dies und das ist verdächtig" zusammenzustellen. Eine Möglichkeit besteht darin, den Bereich zu unterteilen und den Problembereich zu identifizieren, wenn der Bereich tatsächlich zu einem Faktor wird, der die Leistung beeinträchtigt.
Lassen Sie uns die folgenden Maßnahmen für die relevanten Teile ergreifen. Selbst wenn Sie jedes relevante Teil optimieren, ohne darüber nachzudenken, kann der Effekt schwach sein und es kann sich nur um eine Teiloptimierung handeln, sodass wir uns nur auf eine spätere Optimierung vorbereiten.
Hier sind einige Beispiele, die Ihnen helfen, jedes Prinzip zu erreichen. Der Quellcode wird grundsätzlich unter der Annahme von Python 3.6 geschrieben.
HINWEIS: Die Umgebung (z. B. Betriebssystem, Host / VM / WSL) ist beim Vergleich der Messergebnisse jedes Mal anders. Verwenden Sie daher nur Referenzinformationen.
In Prozessen wie dem Durchsuchen einer Sammlung nach einer anderen werden häufig mehrere Schleifen verwendet. Da jedoch der Rechenaufwand für mehrere Schleifen auf O (N ^ 2) und O (N ^ 3) ansteigt, wird dies sofort als Leistungsengpass sichtbar, wenn die Anzahl der Verarbeitungselemente zunimmt.
In einem solchen Fall können Sie den Rechenaufwand von O (N ^ 2) auf O (N) reduzieren, indem Sie beispielsweise HashMap oder Set aus einer der Sammlungen erstellen und diese für die Suche verwenden. Möglicherweise denken Sie, dass die Kosten für die Erstellung einer neuen HashMap / eines neuen Sets für die Anzahl der Elemente in der Sammlung verschwenderisch sind. Wenn Sie sich jedoch die Anzahl der Verarbeitungselemente in der folgenden Tabelle ansehen, können Sie feststellen, dass die Kosten äußerst gering sind.
Elementanzahl | O(N)×2 | O(N^2) |
---|---|---|
10 | 20 | 100 |
100 | 200 | 10,000 |
10,000 | 20,000 | 100,000,000 |
Zunächst wird ein Beispiel für die Doppelschleifenverarbeitung gezeigt. (Verarbeitung wie Messung der Ausführungszeit entfällt)
bad_performance_no1.py
all_files = list(range(1, 100000))
target_files = list(range(50000, 60000))
matched_files = []
#Der Berechnungsbetrag beträgt O.(N^2) -> BAD
for f in all_files:
if f in target_files:
matched_files.append(f)
print(len(matched_files)
Was Sie hier beachten sollten, ist der Teil von if f in target_files:
in der for-Anweisung. Da es sich bei diesem Teil nicht um eine Schleife handelt, scheint der Prozess einmal abgeschlossen zu sein. Um jedoch zu überprüfen, ob die Zieldateien der Liste das Element s enthalten, wird im Durchschnitt N / 2 verwendet, bis ein passendes Element gefunden wird Die Verarbeitung der Elementprüfung wird durchgeführt. Gleiches gilt für die Erfassungsoperationsmethode enthält ()
usw. in Java. Aus diesem Grund kann ** auch wenn die Programmsyntax nicht mehrere Schleifen enthält, die tatsächliche Verarbeitung mehreren Schleifen entsprechen **.
Das folgende Beispiel zeigt das Erstellen eines Sets aus einer Liste, um die Doppelschleife zu verbessern. (Verarbeitung wie Messung der Ausführungszeit entfällt)
good_performance_no1.py
NUM = 1000000
nums = list(range(1, NUM))
# TUNING:Set aus Liste mit Set-Funktion erstellen
# NOTE: //Teilen durch bedeutet, eine ganze Zahl zu halten
expected_nums = set(range(1, NUM//2))
matched_nums = []
#Der Berechnungsbetrag beträgt O.(N)Verbessert zu-> GOOD
start = time.time()
for f in nums:
if f in expected_nums:
matched_nums.append(f)
print(len(matched_nums)
Das Vergleichsergebnis des Schleifenverarbeitungsteils ist unten gezeigt.
Berechnungsbetrag | Ausführungszeit |
---|---|
O(N^2) | 8,228 ms |
O(N) | 4 ms |
Wenn die Anzahl der Verarbeitungselemente N = 100.000 ist, wurde die Geschwindigkeit um etwa das 2000-fache erhöht, indem der Berechnungsbetrag auf O (N) verbessert wurde. Wenn die Anzahl der zu verarbeitenden Datensätze Zehntausende oder mehr beträgt, hat selbst eine solche einfache Schleifenverarbeitung einen großen Verbesserungseffekt.
Wenn Sie einen externen Befehl in einem Programm ausführen, ist die Leistung umso schlechter, je öfter Sie ihn ausführen. Dies liegt daran, dass das Ausführen eines externen Befehls ein kostspieliger Prozess ist, der die Prozesserstellung umfasst.
Hier nehmen wir als Beispiel ein Programm, das darauf abzielt, "die Gesamtbyte-Größe aller Dateien unter / etc anzuzeigen". Sie können die Bytegröße der angegebenen Datei ausgeben, indem Sie die Option --bytes
mit dem Befehl wc angeben.
NOTE: Übrigens bieten die meisten universellen Programmiersprachen, einschließlich Python, Standardfunktionen zum Abrufen der Anzahl der Bytes in einer Datei. Daher muss der Befehl wc nicht verwendet werden. Lesen Sie daher in diesem Beispiel die Annahme, dass "der Befehl wc, bei dem es sich um einen externen Befehl handelt, unbedingt erforderlich ist".
bad_performance_no_batch.py
import pathlib
from subprocess import check_output
cwp = pathlib.Path("/etc")
files = [f for f in cwp.glob("**/*") if f.is_file()]
total_byte = 0
for f in files:
#Führen Sie den Befehl wc für jede Datei aus->Extrem ineffizient
result = check_output(["wc", "--byte", str(f)])
size, file_path = result.split()
total_byte += int(size)
print("file num:", len(files))
print("total byte:", total_byte)
Die Ergebnisse werden am Ende dieses Abschnitts angezeigt. Da der Befehl wc jedoch für jede Datei ausgeführt wird, dauert es einige Sekunden bis einige zehn Sekunden oder länger, wenn die Anzahl der Zieldateien mehrere hundert bis mehrere tausend oder mehr beträgt. Daher ist es notwendig, Wege zu finden, um die Häufigkeit der Ausführung des Befehls wc so weit wie möglich zu reduzieren. Glücklicherweise können Sie mit dem Befehl wc mehrere Dateien mit einer einzigen Befehlsausführung angeben. Daher kann die Anzahl der wc-Befehlsausführungen von der Anzahl der Dateien auf eins gestapelt werden.
Ein Beispiel für die Stapelverarbeitung ist unten dargestellt. (Verarbeitung wie Messung der Ausführungszeit entfällt)
HINWEIS: Das Parsen des Befehls wc im Beispiel ist ziemlich grob und schlampig. Bitte nicht nachahmen.
good_performance_with_batch.py
import pathlib
from subprocess import check_output
cwp = pathlib.Path("/etc")
files = [f for f in cwp.glob("**/*") if f.is_file()]
#Stapelverarbeitung durch Übergabe aller Dateien als Argumente an den Befehl wc
args = [str(f) for f in files]
# NOTE:In Python*Kann die Liste der Argumente als Argument mit erweitern
result = check_output(["wc", "--byte", *args])
total_byte = int(str(result).split(r"\n")[-2].split()[0])
print("file num:", len(files))
print("total byte:", total_byte)
Stapelbefehlsverarbeitung | Ausführungszeit |
---|---|
Keiner | 12,600 ms |
Ja | 338 ms |
Obwohl ich denselben wc-Befehl mit der Option "--bytes" verwendet habe, konnte ich die Verarbeitungszeit durch Stapelverarbeitung auf etwa 1/40 reduzieren.
Es gibt einige Unix / Linux-Befehle und verschiedene Bibliotheken (z. B. SQL, HTTP, E / A), die eine solche Stapelverarbeitung auf irgendeine Weise ermöglichen. Wenn Sie ein Leistungsproblem haben, verwenden Sie es positiv.
Übrigens sind die folgenden Artikel hilfreich, wenn Sie die Stapelverarbeitung externer Befehle fortsetzen möchten. Der Titel sagt "Shell-Skript", aber im Grunde ist es ein Artikel darüber, wie man externe Befehle effizient verwendet.
Um Shell-Skripte zehntausende Male langsamer zu halten - Filter ohne Schleife Fortsetzung: Um das Shell-Skript nicht um Zehntausende Male zu verlangsamen - Die Pipe ist immer noch schnell und leicht zu verstehen
In Situationen mit hohen Leistungsanforderungen werden Systemaufrufe im Zusammenhang mit der E / A-Verarbeitung wie das Lesen und Schreiben von Dateien sowie das Senden und Empfangen von Netzwerken häufig zu großen Engpässen. Daher ist die Pufferung der E / A-Verarbeitung wichtig, um Systemaufrufe zu reduzieren.
HINWEIS: Wichtig ist: "Was können wir tun, um die Anzahl der Systemaufrufe zu verringern?" Und wir verwenden die E / A-Pufferung als wirksames Mittel, um dies zu tun.
Verwenden Sie beispielsweise beim Lesen und Schreiben von Dateien den entsprechenden Pufferungsansatz für jede Sprache.
Ein Beispiel für Python ist unten dargestellt. Im Fall von Python ist der E / A-Puffer standardmäßig aktiviert. Überprüfen Sie den Effekt, indem Sie ihn absichtlich deaktivieren. (Verarbeitung wie Messung der Ausführungszeit entfällt)
# buffering=Wenn 0 angegeben ist, I./O-Puffer wird ungültig
f = open("file.txt", "wb", buffering=0)
s = b"single line"
for i in range(500000):
f.write(s)
f.close()
Die folgenden Vergleichsergebnisse zeigen auch, dass das Aktivieren des E / A-Puffers die Leistung der Schreibverarbeitung um etwa das Zehnfache verbesserte.
Mit oder ohne Puffer | Ausführungszeit |
---|---|
Keiner | 2,223 ms |
Ja | 245 ms |
Skriptsprachen wie Python, Ruby und Perl sind aufgrund des Funktionsmechanismus im Vergleich zu Programmiersprachen wie C und Java, die als nativer Code ausgeführt werden, extrem langsam. Insbesondere ist es nicht ungewöhnlich, dass einfache Operationen und Schleifenverarbeitung zehn- bis hundertmal langsamer sind.
In Situationen, in denen die Verarbeitungsleistung selbst für Skriptsprachen wichtig ist, delegieren wir den Teil, der der Schleifenverarbeitung entspricht, mit der folgenden Methode.
--Verwenden Sie integrierte Funktionen und Sprachfunktionen
HINWEIS: Es mag etwas verwirrend sein, aber es ist eine der Methoden im Zusammenhang mit Prinzip 2, da "die Schleifenverarbeitung der Skripttypsprache von der C-Sprachimplementierung stapelweise verarbeitet wird". Es ist wichtig, so denken zu können, deshalb möchte ich mich täglich dessen bewusst sein.
Vergleichen wir als Beispiel den Fall, in dem die Summe der numerischen Listen durch Schleifenverarbeitung in Python verarbeitet wird, und den Fall, in dem sie von der Summenfunktion ausgeführt wird. (Verarbeitung wie Messung der Ausführungszeit entfällt)
nums = range(50000000)
#Für die Schleifenverarbeitung
total = 0
for n in nums:
total += n
#Für die Summenfunktion
total = sum(nums)
Die Vergleichsergebnisse sind unten gezeigt. Es ist ungefähr 5 mal schneller.
Berechnungsmethode | Ausführungszeit |
---|---|
Schleifenverarbeitung | 3,339 ms |
Summenfunktion | 612 ms |
Da die Summenfunktion in der Sprache C implementiert ist, wird sie mit hoher Geschwindigkeit verarbeitet. Da die Join-Funktion und die Map-Funktion auch in der Sprache C implementiert sind, ist es möglich, die Schleifenverarbeitung auf der Python-Seite zu vermeiden und zu beschleunigen, indem Sie sie gut nutzen.
Abhängig von der Programmiersprache kann der String-Kombinationsprozess, bei dem mehrere Strings zu einem einzigen String kombiniert werden, sehr kostspielig sein.
Bei Python und Java wird jedes Mal, wenn eine Zeichenfolge kombiniert wird, ein neues Zeichenfolgenobjekt generiert, was sehr verschwenderisch ist. Verwenden Sie aus diesem Grund StringBuilder für Java und join () für Python. Einzelheiten finden Sie in den folgenden Artikeln.
[Java] Vergleich der String-Join-Geschwindigkeit So erhöhen Sie die Verarbeitungsgeschwindigkeit von Python 2: Verwenden Sie join (), um eine große Anzahl von Zeichenfolgen zu verketten
Übrigens, seit Java 7 wurde es so optimiert, dass StringBuilder automatisch nur für einzeilige String-Verkettungen wie s =" hallo "+" s "+" !! "
verwendet wird. Beachten Sie jedoch, dass diese Optimierung nicht für die Verarbeitung von Zeichenfolgenverknüpfungen für Variablen außerhalb der Schleife gilt.
Wenn Sie beim Senden einer Anforderung wie HTTP je nach Bibliothek eine HTTP-Bibliothek verwenden, wird für jede Anforderung eine Verbindung erstellt und zerstört. Beispielsweise entspricht request.get () in der Anforderungsbibliothek von Python diesem Muster.
Wenn Sie Dutzende bis Hunderte von Anforderungen pro Sekunde senden, stellen Sie sicher, dass Sie dieselbe Verbindung mit Persistent Connection mit HTTP 1.1 oder höher verwenden.
Vergleichen wir den Fall, in dem die Sitzung in der Python-Anforderungsbibliothek verwendet wird, und den Fall, in dem sie nicht verwendet wird. (Verarbeitung wie Messung der Ausführungszeit entfällt)
import requests
NUM = 3000
url = 'http://localhost:8080/'
#Ohne Sitzung
def without_session():
for i in range(NUM):
response = requests.get(URL)
if response.status_code != 200:
raise Exception("Error")
#Mit Sitzung
def with_session():
with requests.Session() as ses:
for i in range(NUM):
response = ses.get(URL)
if response.status_code != 200:
raise Exception("Error")
without_session()
with_session()
Die Vergleichsergebnisse sind unten gezeigt. Befindet sich ein Webserver auf demselben Host (lokal), ist dieser ungefähr 1,2-mal schneller, und wenn sich im Internet ein Webserver befindet (Internet), ist er ungefähr 2-mal schneller. Sie sehen, dass der Effekt umso größer ist, je höher die Verbindungskosten zum Server sind.
Berechnungsbetrag | Ausführungszeit |
---|---|
Internet +Keine Sitzung | 7,000 ms |
Internet +Mit Sitzung | 3.769 ms |
Local +Keine Sitzung | 6,606 ms |
Local +Mit Sitzung | 5.538 ms |
Themen zur Leistungsoptimierung, die nicht direkt mit Prinzipien zusammenhängen.
Wenn das DEBUG-Protokoll etwas anderes als eine feste Zeichenfolge verwendet, wird der Teil, der das Argument übergibt, berechnet, sodass Verarbeitungskosten anfallen, auch wenn das DEBUG-Protokoll nicht auf Protokollebene ausgegeben wird. Insbesondere wenn sich ein solches DEBUG-Protokoll an einem Ort befindet, an dem es tausende oder zehntausende Male pro Sekunde ausgeführt wird, sind die Auswirkungen auf die Leistung sehr groß.
Kombinieren Sie bei der Eingabe der DEBUG-Protokollausgabe, die eine Berechnung umfasst, unbedingt die if-Anweisung der Protokollstufenprüfung, damit die Berechnungsverarbeitung nicht auf der Protokollebene erfolgt, auf der keine DEBUG-Protokollausgabe ausgeführt wird.
l = [1, 2, 3, 4, 5]
# BAD:
#Führt die Gesamtlistenberechnung und die Verkettung von Zeichenfolgen unabhängig von der Protokollstufe durch
logging.debug("Sum=" + sum(l))
# GOOD:
if logging.isdebug():
#Für DEBUG-Ebene überhaupt nicht implementiert
logging.debug("Sum=" + sum(l))
# GOOD:Bei einer festen Zeichenfolge findet keine Berechnung statt, daher ist dies in Ordnung
logging.debug("Entered the function A")
HINWEIS: Dies ist nicht der Fall bei verzögert ausgewerteten Programmiersprachen.
Recommended Posts