langsam – chatgpz.de – KI-Tools & Praxiswissen http://chatgpz.de Unabhängige Tests, Anleitungen und Hintergründe zu KI-Tools. Fri, 06 Feb 2026 10:27:31 +0000 de-DE hourly 1 https://wordpress.org/?v=6.4.2 234900186 Warum ist mein Code so langsam? Ein Leitfaden zur Py-Spy-Python-Profilerstellung http://chatgpz.de/artificial-intelligence/warum-ist-mein-code-so-langsam-ein-leitfaden-zur-py-spy-python-profilerstellung/ http://chatgpz.de/artificial-intelligence/warum-ist-mein-code-so-langsam-ein-leitfaden-zur-py-spy-python-profilerstellung/#respond Fri, 06 Feb 2026 10:27:31 +0000 https://chatgpz.de/artificial-intelligence/warum-ist-mein-code-so-langsam-ein-leitfaden-zur-py-spy-python-profilerstellung/ [ad_1]

Frustrierende Probleme beim Debuggen im Information-Science-Code sind keine Syntaxfehler oder logische Fehler. Sie stammen vielmehr aus Code, der genau das tut, was er tun soll, sich dabei aber viel Zeit lässt.

Funktioneller, aber ineffizienter Code kann einen massiven Engpass in einem Information-Science-Workflow darstellen. In diesem Artikel werde ich eine kurze Einführung und einen Rundgang geben py-spyein leistungsstarkes Instrument zur Profilierung Ihres Python-Codes. Es kann genau bestimmen, wo Ihr Programm die meiste Zeit verbringt, sodass Ineffizienzen erkannt und behoben werden können.

Beispielproblem

Lassen Sie uns eine einfache Forschungsfrage aufstellen, um Code zu schreiben:

„Welcher Abflughafen hat bei allen Flügen zwischen US-Bundesstaaten und Territorien im Durchschnitt die längsten Flüge?“

Unten finden Sie ein einfaches Python-Skript zur Beantwortung dieser Forschungsfrage unter Verwendung von Daten, die aus dem abgerufen wurden Büro für Verkehrsstatistik (BTS). Der Datensatz besteht aus Daten aller Flüge innerhalb der US-Bundesstaaten und Territorien zwischen Januar und Juni 2025 mit Informationen zu den Abflug- und Zielflughäfen. Es sind ungefähr 3,5 Millionen Zeilen.

Es berechnet die Haversine-Distanz – der kürzeste Abstand zwischen zwei Punkten auf einer Kugel – für jeden Flug. Anschließend gruppiert es die Ergebnisse nach Abflughafen, um die durchschnittliche Entfernung zu ermitteln, und meldet die fünf besten.

import pandas as pd  
import math  
import time  
  
  
def haversine(lat_1, lon_1, lat_2, lon_2):  
    """Calculate the Haversine Distance between two latitude and longitude factors"""  
    lat_1_rad = math.radians(lat_1)  
    lon_1_rad = math.radians(lon_1)  
    lat_2_rad = math.radians(lat_2)  
    lon_2_rad = math.radians(lon_2)  
  
    delta_lat = lat_2_rad - lat_1_rad  
    delta_lon = lon_2_rad - lon_1_rad  
  
    R = 6371  # Radius of the earth in km  
  
    return 2*R*math.asin(math.sqrt(math.sin(delta_lat/2)**2 + math.cos(lat_1_rad)*math.cos(lat_2_rad)*(math.sin(delta_lon/2))**2))  
  
  
if __name__ == '__main__':  
    # Load in flight information to a dataframe  
    flight_data_file = r"./information/2025_flight_data.csv"  
    flights_df = pd.read_csv(flight_data_file)  
  
    # Begin timer to see how lengthy evaluation takes  
    begin = time.time()  
  
    # Calculate the haversine distance between every flight's begin and finish airport  
    haversine_dists = ()  
    for i, row in flights_df.iterrows():  
        haversine_dists.append(haversine(lat_1=row("LATITUDE_ORIGIN"),  
                                         lon_1=row("LONGITUDE_ORIGIN"),  
                                         lat_2=row("LATITUDE_DEST"),  
                                         lon_2=row("LONGITUDE_DEST")))  
  
    flights_df("Distance") = haversine_dists  
  
    # Get end result by grouping by origin airport, taking the common flight distance and      printing the highest 5  
    end result = (  
        flights_df  
        .groupby('DISPLAY_AIRPORT_NAME_ORIGIN').agg(avg_dist=('Distance', 'imply'))  
        .sort_values('avg_dist', ascending=False)  
    )  
  
    print(end result.head(5))  
  
    # Finish timer and print evaluation time  
    finish = time.time()  
    print(f"Took {finish - begin} s")

Das Ausführen dieses Codes liefert die folgende Ausgabe:

                                        avg_dist
DISPLAY_AIRPORT_NAME_ORIGIN                     
Pago Pago Worldwide              4202.493567
Guam Worldwide                   3142.363005
Luis Munoz Marin Worldwide       2386.141780
Ted Stevens Anchorage Worldwide  2246.530036
Daniel Okay Inouye Worldwide        2211.857407
Took 169.8935534954071 s

Diese Ergebnisse sind sinnvoll, da sich die aufgeführten Flughäfen jeweils in Amerikanisch-Samoa, Guam, Puerto Rico, Alaska und Hawaii befinden. Dies sind alles Orte außerhalb der angrenzenden Vereinigten Staaten, an denen man mit langen durchschnittlichen Flugentfernungen rechnen muss.

Das Downside hierbei sind nicht die Ergebnisse – die gültig sind – sondern die Ausführungszeit: quick drei Minuten! Während drei Minuten für einen einmaligen Durchlauf akzeptabel sein könnten, werden sie während der Entwicklung zu einem Produktivitätskiller. Stellen Sie sich dies als Teil einer längeren Datenpipeline vor. Jedes Mal, wenn ein Parameter angepasst, ein Fehler behoben oder eine Zelle erneut ausgeführt wird, sind Sie gezwungen, untätig zu bleiben, während das Programm ausgeführt wird. Diese Reibung unterbricht Ihren Fluss und macht eine schnelle Analyse zu einer ganztägigen Angelegenheit.

Nun wollen wir sehen, wie py-spy kann uns helfen, genau zu diagnostizieren, welche Leitungen so lange dauern.

Was ist Py-Spy?

Um zu verstehen, was py-spy Es hilft, zu vergleichen, was es tut und welche Vorteile es mit sich bringt py-spy zum integrierten Python-Profiler cProfile.

  • cProfile: Das ist ein Tracing-Profilerdie bei jedem Funktionsaufruf ähnlich wie eine Stoppuhr funktioniert. Die Zeit zwischen jedem Funktionsaufruf und jeder Rückgabe wird gemessen und gemeldet. Dies ist zwar sehr genau, verursacht jedoch einen erheblichen Mehraufwand, da der Profiler ständig anhalten und Daten aufzeichnen muss, was das Skript erheblich verlangsamen kann.
  • py-spy: Das ist ein Sampling-ProfilerEs funktioniert ähnlich wie eine Hochgeschwindigkeitskamera, die das gesamte Programm auf einmal betrachtet. py-spy befindet sich vollständig außerhalb des laufenden Python-Skripts und erstellt hochfrequente Schnappschüsse des Programmstatus. Es betrachtet den gesamten „Name Stack“, um genau zu sehen, welche Codezeile ausgeführt wird und welche Funktion sie aufgerufen hat, bis hin zur obersten Ebene.

Laufender Py-Spion

Um zu laufen py-spy auf einem Python-Skript, dem py-spy Die Bibliothek muss in der Python-Umgebung installiert werden.

pip set up py-spy

Sobald die py-spy Wenn die Bibliothek installiert ist, kann unser Skript durch Ausführen des folgenden Befehls im Terminal profiliert werden:

py-spy file -o profile.svg -r 100 -- python fundamental.py

Hier sehen Sie, was jeder Teil dieses Befehls tatsächlich bewirkt:

  • py-spy: Ruft das Instrument auf.
  • file: Das sagt py-spy um den „Aufzeichnungsmodus“ zu verwenden, der das Programm während der Ausführung kontinuierlich überwacht und die Daten speichert.
  • -o profile.svg: Dies gibt den Namen und das Format der Ausgabedatei an und weist an, die Ergebnisse als SVG-Datei mit dem Namen auszugeben profile.svg.
  • -r 100: Dies gibt die Abtastrate an und legt sie auf 100 Mal professional Sekunde fest. Das bedeutet das py-spy überprüft 100 Mal professional Sekunde, was das Programm tut.
  • --: Dies trennt die py-spy Befehl aus dem Python-Skriptbefehl. Es erzählt py-spy dass alles, was auf dieses Flag folgt, der auszuführende Befehl und keine Argumente dafür ist py-spy selbst.
  • python fundamental.py: Dies ist der Befehl zum Ausführen des Python-Skripts, mit dem ein Profil erstellt werden soll py-spyin diesem Fall läuft fundamental.py.

Notiz: Wenn es unter Linux läuft, sudo Privilegien sind oft eine Voraussetzung für die Ausführung py-spyaus Sicherheitsgründen.

Nachdem dieser Befehl ausgeführt wurde, wird eine Ausgabedatei erstellt profile.svg wird angezeigt, was uns einen tieferen Einblick in die Teile des Codes ermöglicht, die am längsten dauern.

Py-Spy-Ausgabe

Ausgabe des Eiszapfendiagramms von py-spy

Ausgabe öffnen profile.svg verrät die Visualisierung, die py-spy hat erstellt, wie viel Zeit unser Programm in verschiedenen Teilen des Codes verbracht hat. Dies wird als a bezeichnet Eiszapfendiagramm (oder manchmal a Flammendiagramm wenn die y-Achse invertiert ist) und wird wie folgt interpretiert:

  • Barren: Jeder farbige Balken stellt eine bestimmte Funktion dar, die während der Ausführung des Programms aufgerufen wurde.
  • X-Achse (Bevölkerung): Die horizontale Achse stellt die Sammlung aller während der Profilierung entnommenen Proben dar. Sie sind so gruppiert, dass die Breite eines bestimmten Balkens den Anteil der Gesamtproben darstellt, die das Programm in der durch diesen Balken dargestellten Funktion hatte. Notiz: Das ist nicht eine Zeitleiste; Die Reihenfolge stellt nicht dar, wann die Funktion aufgerufen wurde, sondern nur die insgesamt aufgewendete Zeit.
  • Y-Achse (Stapeltiefe): Die vertikale Achse stellt den Aufrufstapel dar. Die obere Leiste mit der Bezeichnung „Alle“ stellt das gesamte Programm dar, und die Balken darunter stellen Funktionen dar, die von „Alle“ aufgerufen werden. Dies wird rekursiv fortgesetzt, wobei jeder Balken in die Funktionen zerlegt wird, die während seiner Ausführung aufgerufen wurden. Die ganz untere Leiste zeigt die Funktion, die zum Zeitpunkt der Probenentnahme tatsächlich auf der CPU ausgeführt wurde.

Interaktion mit dem Diagramm

Während das Bild oben statisch ist, handelt es sich um das tatsächliche .svg Datei generiert von py-spy ist vollständig interaktiv. Wenn Sie es in einem Webbrowser öffnen, können Sie:

  • Suchen (Strg+F): Markieren Sie bestimmte Funktionen, um zu sehen, wo sie im Stapel angezeigt werden.
  • Zoom: Klicken Sie auf eine beliebige Leiste, um diese bestimmte Funktion und ihre untergeordneten Funktionen zu vergrößern und so komplexe Teile des Aufrufstapels zu isolieren.
  • Schweben: Wenn Sie den Mauszeiger über eine beliebige Leiste bewegen, werden der spezifische Funktionsname, der Dateipfad, die Zeilennummer und der genaue Prozentsatz der verbrauchten Zeit angezeigt.

Die wichtigste Regel zum Lesen des Eiszapfendiagramms lautet einfach: Je breiter der Balken, desto häufiger ist die Funktion. Wenn sich eine Funktionsleiste über 50 % der Breite des Diagramms erstreckt, bedeutet dies, dass das Programm 50 % der gesamten Laufzeit mit der Ausführung dieser Funktion beschäftigt warfare.

Diagnose

Aus dem Eiszapfendiagramm oben können wir ersehen, dass der Balken die Pandas darstellt iterrows() Die Funktion ist spürbar umfangreich. Bewegen Sie den Mauszeiger über diese Leiste, wenn Sie das anzeigen profile.svg Datei zeigt, dass der wahre Anteil für diese Funktion warfare 68,36 %. Es wurden additionally über 2/3 der Laufzeit im verbracht iterrows() Funktion. Intuitiv macht dieser Engpass Sinn, da iterrows() erstellt für jede einzelne Zeile in der Schleife ein Pandas-Serienobjekt, was einen enormen Mehraufwand verursacht. Daraus ergibt sich ein klares Ziel, die Laufzeit des Skripts zu optimieren.

Optimierung des Skripts

Der klarste Weg zur Optimierung dieses Skripts basierend auf den Erkenntnissen py-spy ist, mit dem Gebrauch aufzuhören iterrows() um jede Zeile zu durchlaufen, um diesen Haversinus-Abstand zu berechnen. Stattdessen sollte es durch eine vektorisierte Berechnung mit NumPy ersetzt werden, die die Berechnung für jede Zeile mit nur einem Funktionsaufruf durchführt. Die vorzunehmenden Änderungen sind additionally:

  • Schreiben Sie das um haversine() Funktion, um vektorisierte und effiziente NumPy-Operationen auf C-Ebene zu verwenden, die die Übergabe ganzer Arrays statt jeweils eines Koordinatensatzes ermöglichen.
  • Ersetzen Sie die iterrows() Schleife mit einem einzigen Aufruf dieser neu vektorisierten Schleife haversine() Funktion.
import pandas as pd  
import numpy as np  
import time  
  
  
def haversine(lat_1, lon_1, lat_2, lon_2):  
    """Calculate the Haversine Distance between two latitude and longitude factors"""  
    lat_1_rad = np.radians(lat_1)  
    lon_1_rad = np.radians(lon_1)  
    lat_2_rad = np.radians(lat_2)  
    lon_2_rad = np.radians(lon_2)  
  
    delta_lat = lat_2_rad - lat_1_rad  
    delta_lon = lon_2_rad - lon_1_rad  
  
    R = 6371  # Radius of the earth in km  
  
    return 2*R*np.asin(np.sqrt(np.sin(delta_lat/2)**2 + np.cos(lat_1_rad)*np.cos(lat_2_rad)*(np.sin(delta_lon/2))**2))  
  
  
if __name__ == '__main__':  
    # Load in flight information to a dataframe  
    flight_data_file = r"./information/2025_flight_data.csv"  
    flights_df = pd.read_csv(flight_data_file)  
  
    # Begin timer to see how lengthy evaluation takes  
    begin = time.time()  
  
    # Calculate the haversine distance between every flight's begin and finish airport  
    flights_df("Distance") = haversine(lat_1=flights_df("LATITUDE_ORIGIN"),  
                                       lon_1=flights_df("LONGITUDE_ORIGIN"),  
                                       lat_2=flights_df("LATITUDE_DEST"),  
                                       lon_2=flights_df("LONGITUDE_DEST"))  
  
    # Get end result by grouping by origin airport, taking the common flight distance and      printing the highest 5  
    end result = (  
        flights_df  
        .groupby('DISPLAY_AIRPORT_NAME_ORIGIN').agg(avg_dist=('Distance', 'imply'))  
        .sort_values('avg_dist', ascending=False)  
    )  
  
    print(end result.head(5))  
  
    # Finish timer and print evaluation time  
    finish = time.time()  
    print(f"Took {finish - begin} s")

Das Ausführen dieses Codes liefert die folgende Ausgabe:

                                        avg_dist
DISPLAY_AIRPORT_NAME_ORIGIN                     
Pago Pago Worldwide              4202.493567
Guam Worldwide                   3142.363005
Luis Munoz Marin Worldwide       2386.141780
Ted Stevens Anchorage Worldwide  2246.530036
Daniel Okay Inouye Worldwide        2211.857407
Took 0.5649983882904053 s

Diese Ergebnisse sind identisch mit den Ergebnissen vor der Optimierung des Codes, aber anstatt quick drei Minuten für die Verarbeitung zu benötigen, es hat etwas mehr als eine halbe Sekunde gedauert!

Blick nach vorn

Wenn Sie dies aus der Zukunft (Ende 2026 oder später) lesen, prüfen Sie, ob Sie Python 3.15 oder neuer verwenden. Es wird erwartet, dass Python 3.15 einen nativen Sampling-Profiler in die Standardbibliothek einführt, der ähnliche Funktionen bietet wie py-spy ohne dass eine externe Set up erforderlich ist. Für alle, die Python 3.14 oder älter verwenden py-spy bleibt der Goldstandard.

In diesem Artikel wurde ein Instrument zur Bewältigung einer häufigen Frustration in der Datenwissenschaft untersucht – ein Skript, das wie vorgesehen funktioniert, aber ineffizient geschrieben ist und lange zur Ausführung benötigt. Es wurde ein Beispielskript bereitgestellt, um herauszufinden, welche US-Abflughäfen gemäß der Haversine-Entfernung die längste durchschnittliche Flugentfernung haben. Dieses Skript funktionierte wie erwartet, die Ausführung dauerte jedoch quick drei Minuten.

Mit der py-spy Mit dem Python-Profiler konnten wir herausfinden, dass die Ursache für die Ineffizienz in der Verwendung von lag iterrows() Funktion. Durch Ersetzen iterrows() Mit einer effizienteren vektorisierten Berechnung der Haversine-Distanz wurde die Laufzeit von drei Minuten auf etwas mehr als eine halbe Sekunde optimiert.

Siehe meine GitHub-Repository für den Code aus diesem Artikel, einschließlich der Vorverarbeitung der Rohdaten von BTS.

Vielen Dank fürs Lesen!

Datenquellen

Die Daten des Bureau of Transportation Statistics (BTS) sind ein Werk der US-Bundesregierung und unter gemeinfrei 17 USC § 105. Die Nutzung, Weitergabe und Anpassung ist ohne Urheberrechtsbeschränkung kostenlos.

[ad_2]

]]>
http://chatgpz.de/artificial-intelligence/warum-ist-mein-code-so-langsam-ein-leitfaden-zur-py-spy-python-profilerstellung/feed/ 0 16105
Denken Sie, dass Ihr Python-Code langsam ist? Hören Sie auf zu raten und beginnen Sie mit dem Messen http://chatgpz.de/machine-learning/denken-sie-dass-ihr-python-code-langsam-ist-hoeren-sie-auf-zu-raten-und-beginnen-sie-mit-dem-messen/ http://chatgpz.de/machine-learning/denken-sie-dass-ihr-python-code-langsam-ist-hoeren-sie-auf-zu-raten-und-beginnen-sie-mit-dem-messen/#respond Sat, 27 Dec 2025 00:56:34 +0000 https://chatgpz.de/machine-learning/denken-sie-dass-ihr-python-code-langsam-ist-hoeren-sie-auf-zu-raten-und-beginnen-sie-mit-dem-messen/ [ad_1]

Ich habe neulich an einem Drehbuch gearbeitet und es hat mich wahnsinnig gemacht. Es hat zwar funktioniert, aber es conflict nur … langsam. Wirklich langsam. Ich hatte das Gefühl, dass es viel schneller gehen könnte, wenn ich es herausfinden könnte Wo die Verzögerung conflict.

Mein erster Gedanke conflict, mit den Optimierungen zu beginnen. Ich konnte das Laden der Daten optimieren. Oder die for-Schleife umschreiben? Aber ich habe mich zurückgehalten. Ich bin schon einmal in diese Falle getappt und habe Stunden damit verbracht, einen Codeabschnitt zu „optimieren“, nur um dann festzustellen, dass er kaum einen Unterschied zur Gesamtlaufzeit macht. Donald Knuth hatte Recht, als er sagte: „Vorzeitige Optimierung ist die Wurzel allen Übels.“

Ich habe mich für einen methodischeren Ansatz entschieden. Anstatt zu raten, wollte ich es sicher herausfinden. Ich musste ein Profil des Codes erstellen, um konkrete Daten darüber zu erhalten, welche Funktionen genau die meisten Taktzyklen verbrauchten.

In diesem Artikel werde ich Sie durch den genauen Prozess führen, den ich verwendet habe. Wir nehmen ein bewusst langsames Python-Skript und verwenden zwei fantastische Instruments, um seine Engpässe mit chirurgischer Präzision zu lokalisieren.

Das erste dieser Instruments heißt cProfileein leistungsstarker, in Python integrierter Profiler. Der andere heißt Schlangenvisz, A geniales Werkzeug Das wandelt die Ausgabe des Profilers in eine interaktive visuelle Karte um.

Einrichten einer Entwicklungsumgebung

Bevor wir mit dem Codieren beginnen, richten wir unsere Entwicklungsumgebung ein. Die beste Vorgehensweise besteht darin, eine separate Python-Umgebung zu erstellen, in der Sie die erforderliche Software program installieren und experimentieren können, in dem Wissen, dass alles, was Sie tun, keine Auswirkungen auf den Relaxation Ihres Techniques hat. Ich werde dafür Conda verwenden, aber Sie können jede Methode verwenden, mit der Sie vertraut sind.

#create our check setting
conda create -n profiling_lab python=3.11 -y

# Now activate it
conda activate profiling_lab

Nachdem wir nun unsere Umgebung eingerichtet haben, müssen wir Snakeviz für unsere Visualisierungen und Numpy für das Beispielskript installieren. cProfile ist bereits in Python enthalten, daher gibt es dort nichts weiter zu tun. Da wir unsere Skripte mit einem Jupyter-Pocket book ausführen, werden wir dieses auch installieren.

# Set up our visualization instrument and numpy
pip set up snakeviz numpy jupyter

Geben Sie nun ein jupyter pocket book in Ihre Eingabeaufforderung ein. In Ihrem Browser sollte ein Jupiter-Notizbuch geöffnet sein. Wenn dies nicht automatisch geschieht, wird Ihnen danach wahrscheinlich ein Bildschirm voller Informationen angezeigt jupyter pocket book Befehl. Ganz unten finden Sie eine URL, die Sie kopieren und in Ihren Browser einfügen sollten, um das Jupyter Pocket book zu starten.

Ihre URL wird sich von meiner unterscheiden, aber sie sollte in etwa so aussehen:-

http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69da

Nachdem unsere Instruments bereit sind, ist es an der Zeit, einen Blick auf den Code zu werfen, den wir reparieren werden.

Unser „Drawback“-Skript

Um unsere Profiling-Instruments ordnungsgemäß zu testen, benötigen wir ein Skript, das eindeutige Leistungsprobleme aufweist. Ich habe ein einfaches Programm geschrieben, das Verarbeitungsprobleme mit Speicher, Iteration und CPU-Zyklen simuliert, was es zu einem perfekten Kandidaten für unsere Untersuchung macht.

# run_all_systems.py
import time
import math

# ===================================================================
CPU_ITERATIONS = 34552942
STRING_ITERATIONS = 46658100
LOOP_ITERATIONS = 171796964
# ===================================================================

# --- Activity 1: A Calibrated CPU-Certain Bottleneck ---
def cpu_heavy_task(iterations):
    print("  -> Operating CPU-bound job...")
    end result = 0
    for i in vary(iterations):
        end result += math.sin(i) * math.cos(i) + math.sqrt(i)
    return end result

# --- Activity 2: A Calibrated Reminiscence/String Bottleneck ---
def memory_heavy_string_task(iterations):
    print("  -> Operating Reminiscence/String-bound job...")
    report = ""
    chunk = "report_item_abcdefg_123456789_"
    for i in vary(iterations):
        report += f"|{chunk}{i}"
    return report

# --- Activity 3: A Calibrated "Thousand Cuts" Iteration Bottleneck ---
def simulate_tiny_op(n):
    move

def iteration_heavy_task(iterations):
    print("  -> Operating Iteration-bound job...")
    for i in vary(iterations):
        simulate_tiny_op(i)
    return "OK"

# --- Fundamental Orchestrator ---
def run_all_systems():
    print("--- Beginning FINAL SLOW Balanced Showcase ---")
    
    cpu_result = cpu_heavy_task(iterations=CPU_ITERATIONS)
    string_result = memory_heavy_string_task(iterations=STRING_ITERATIONS)
    iteration_result = iteration_heavy_task(iterations=LOOP_ITERATIONS)

    print("--- FINAL SLOW Balanced Showcase Completed ---")

Schritt 1: Sammeln der Daten mit cProfile

Unser erstes Software, cProfile, ist ein in Python integrierter deterministischer Profiler. Wir können es über den Code ausführen, um unser Skript auszuführen und detaillierte Statistiken zu jedem Funktionsaufruf aufzuzeichnen.

import cProfile, pstats, io

pr = cProfile.Profile()
pr.allow()

# Run the perform you wish to profile
run_all_systems()

pr.disable()

# Dump stats to a string and print the highest 10 by cumulative time
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats("cumtime")
ps.print_stats(10)
print(s.getvalue())

Hier ist die Ausgabe.

--- Beginning FINAL SLOW Balanced Showcase ---
  -> Operating CPU-bound job...
  -> Operating Reminiscence/String-bound job...
  -> Operating Iteration-bound job...
--- FINAL SLOW Balanced Showcase Completed ---
         275455984 perform calls in 30.497 seconds

   Ordered by: cumulative time
   Listing diminished from 47 to 10 resulting from restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(perform)
        2    0.000    0.000   30.520   15.260 /house/tom/.native/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3541(run_code)
        2    0.000    0.000   30.520   15.260 {built-in technique builtins.exec}
        1    0.000    0.000   30.497   30.497 /tmp/ipykernel_173802/1743829582.py:41(run_all_systems)
        1    9.652    9.652   14.394   14.394 /tmp/ipykernel_173802/1743829582.py:34(iteration_heavy_task)
        1    7.232    7.232   12.211   12.211 /tmp/ipykernel_173802/1743829582.py:14(cpu_heavy_task)
171796964    4.742    0.000    4.742    0.000 /tmp/ipykernel_173802/1743829582.py:31(simulate_tiny_op)
        1    3.891    3.891    3.892    3.892 /tmp/ipykernel_173802/1743829582.py:22(memory_heavy_string_task)
 34552942    1.888    0.000    1.888    0.000 {built-in technique math.sin}
 34552942    1.820    0.000    1.820    0.000 {built-in technique math.cos}
 34552942    1.271    0.000    1.271    0.000 {built-in technique math.sqrt}

Wir haben eine Reihe von Zahlen, die schwer zu interpretieren sein können. Hier kommt Snakeviz ins Spiel.

Schritt 2: Visualisierung des Engpasses mit Snakeviz

Hier geschieht die Magie. Snakeviz nimmt die Ausgabe unserer Profilierungsdatei und wandelt sie in ein interaktives, browserbasiertes Diagramm um, um das Auffinden von Engpässen zu erleichtern.

Nutzen wir additionally dieses Software, um zu visualisieren, was wir haben. Da ich ein Jupyter-Pocket book verwende, müssen wir es zuerst laden.

%load_ext snakeviz

Und wir führen es so aus.

%%snakeviz
predominant()

Die Ausgabe besteht aus zwei Teilen. Zuerst ist eine Visualisierung wie diese.

Bild vom Autor

Was Sie sehen, ist ein „Eiszapfen“-Diagramm von oben nach unten. Von oben nach unten stellt es die Anrufhierarchie dar.

Ganz oben: Python führt unser Skript aus ().

Als nächstes: die __main__-Ausführung des Skripts (:1()). Dann die Funktion run_all_systems. Darin werden zwei Schlüsselfunktionen aufgerufen: iteration_heavy_task und cpu_heavy_task.

Der speicherintensive Verarbeitungsteil ist im Diagramm nicht gekennzeichnet. Das liegt daran, dass der Zeitanteil, der mit dieser Aufgabe verbunden ist, viel kleiner ist als der Zeitanteil, der auf die anderen beiden intensiven Funktionen entfällt. Als Ergebnis sehen wir rechts vom cpu_heavy_task-Block einen viel kleineren, unbeschrifteten Block.

Beachten Sie, dass es für die Analyse auch einen Snakeviz-Diagrammstil namens a gibt Sunburst-Diagramm. Es ähnelt ein wenig einem Kreisdiagramm, enthält jedoch eine Reihe immer größerer konzentrischer Kreise und Bögen. Die Idee dahinter ist, dass die Zeit, die Funktionen zum Ausführen benötigen, durch die Winkelausdehnung der Bogengröße des Kreises dargestellt wird. Die Wurzelfunktion ist ein Kreis in der Mitte von nämlich. Die Root-Funktion wird ausgeführt, indem sie die darunter liegenden Unterfunktionen usw. aufruft. Wir werden uns in diesem Artikel nicht mit diesem Anzeigetyp befassen.

Eine solche visuelle Bestätigung kann so viel wirkungsvoller sein, als auf eine Zahlentabelle zu starren. Ich musste nicht mehr raten, wo ich suchen sollte; Die Daten starrten mich direkt ins Gesicht.

Auf die Visualisierung folgt schnell ein Textblock, der die Zeitabläufe für verschiedene Teile Ihres Codes detailliert beschreibt, ähnlich wie die Ausgabe des cprofile-Instruments. Ich zeige hier nur die ersten etwa ein Dutzend Zeilen, da es insgesamt über 30 waren.

ncalls tottime percall cumtime percall filename:lineno(perform)
----------------------------------------------------------------
1 9.581 9.581 14.3 14.3 1062495604.py:34(iteration_heavy_task)
1 7.868 7.868 12.92 12.92 1062495604.py:14(cpu_heavy_task)
171796964 4.717 2.745e-08 4.717 2.745e-08 1062495604.py:31(simulate_tiny_op)
1 3.848 3.848 3.848 3.848 1062495604.py:22(memory_heavy_string_task)
34552942 1.91 5.527e-08 1.91 5.527e-08 ~:0(<built-in technique math.sin>)
34552942 1.836 5.313e-08 1.836 5.313e-08 ~:0(<built-in technique math.cos>)
34552942 1.305 3.778e-08 1.305 3.778e-08 ~:0(<built-in technique math.sqrt>)
1 0.02127 0.02127 31.09 31.09 <string>:1(<module>)
4 0.0001764 4.409e-05 0.0001764 4.409e-05 socket.py:626(ship)
10 0.000123 1.23e-05 0.0004568 4.568e-05 iostream.py:655(write)
4 4.594e-05 1.148e-05 0.0002735 6.838e-05 iostream.py:259(schedule)
...
...
...

Schritt 3: Die Lösung

Instruments wie Cprofiler und Snakeviz verraten es Ihnen natürlich nicht Wie um Ihre Leistungsprobleme zu beheben, aber da ich nun genau wusste, wo die Probleme lagen, konnte ich gezielte Korrekturen vornehmen.

# final_showcase_fixed_v2.py
import time
import math
import numpy as np

# ===================================================================
CPU_ITERATIONS = 34552942
STRING_ITERATIONS = 46658100
LOOP_ITERATIONS = 171796964
# ===================================================================

# --- Repair 1: Vectorization for the CPU-Certain Activity ---
def cpu_heavy_task_fixed(iterations):
    """
    Fastened by utilizing NumPy to carry out the advanced math on a whole array
    without delay, in extremely optimized C code as an alternative of a Python loop.
    """
    print("  -> Operating CPU-bound job...")
    # Create an array of numbers from 0 to iterations-1
    i = np.arange(iterations, dtype=np.float64)
    # The identical calculation, however vectorized, is orders of magnitude quicker
    result_array = np.sin(i) * np.cos(i) + np.sqrt(i)
    return np.sum(result_array)

# --- Repair 2: Environment friendly String Becoming a member of ---
def memory_heavy_string_task_fixed(iterations):
    """
    Fastened by utilizing an inventory comprehension and a single, environment friendly ''.be a part of() name.
    This avoids creating hundreds of thousands of intermediate string objects.
    """
    print("  -> Operating Reminiscence/String-bound job...")
    chunk = "report_item_abcdefg_123456789_"
    # A listing comprehension is quick and memory-efficient
    elements = (f"|{chunk}{i}" for i in vary(iterations))
    return "".be a part of(elements)

# --- Repair 3: Eliminating the "Thousand Cuts" Loop ---
def iteration_heavy_task_fixed(iterations):
    """
    Fastened by recognizing the duty is usually a no-op or a bulk operation.
    In a real-world state of affairs, you'd discover a strategy to keep away from the loop solely.
    Right here, we show the repair by merely eradicating the pointless loop.
    The aim is to indicate the price of the loop itself was the issue.
    """
    print("  -> Operating Iteration-bound job...")
    # The repair is to discover a bulk operation or eradicate the necessity for the loop.
    # Because the unique perform did nothing, the repair is to do nothing, however quicker.
    return "OK"

# --- Fundamental Orchestrator ---
def run_all_systems():
    """
    The primary orchestrator now calls the FAST variations of the duties.
    """
    print("--- Beginning FINAL FAST Balanced Showcase ---")
    
    cpu_result = cpu_heavy_task_fixed(iterations=CPU_ITERATIONS)
    string_result = memory_heavy_string_task_fixed(iterations=STRING_ITERATIONS)
    iteration_result = iteration_heavy_task_fixed(iterations=LOOP_ITERATIONS)

    print("--- FINAL FAST Balanced Showcase Completed ---")

Jetzt können wir den cprofiler für unseren aktualisierten Code erneut ausführen.

import cProfile, pstats, io

pr = cProfile.Profile()
pr.allow()

# Run the perform you wish to profile
run_all_systems()

pr.disable()

# Dump stats to a string and print the highest 10 by cumulative time
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats("cumtime")
ps.print_stats(10)
print(s.getvalue())

#
# begin of output
#

--- Beginning FINAL FAST Balanced Showcase ---
  -> Operating CPU-bound job...
  -> Operating Reminiscence/String-bound job...
  -> Operating Iteration-bound job...
--- FINAL FAST Balanced Showcase Completed ---
         197 perform calls in 6.063 seconds

   Ordered by: cumulative time
   Listing diminished from 52 to 10 resulting from restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(perform)
        2    0.000    0.000    6.063    3.031 /house/tom/.native/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3541(run_code)
        2    0.000    0.000    6.063    3.031 {built-in technique builtins.exec}
        1    0.002    0.002    6.063    6.063 /tmp/ipykernel_173802/1803406806.py:1(<module>)
        1    0.402    0.402    6.061    6.061 /tmp/ipykernel_173802/3782967348.py:52(run_all_systems)
        1    0.000    0.000    5.152    5.152 /tmp/ipykernel_173802/3782967348.py:27(memory_heavy_string_task_fixed)
        1    4.135    4.135    4.135    4.135 /tmp/ipykernel_173802/3782967348.py:35(<listcomp>)
        1    1.017    1.017    1.017    1.017 {technique 'be a part of' of 'str' objects}
        1    0.446    0.446    0.505    0.505 /tmp/ipykernel_173802/3782967348.py:14(cpu_heavy_task_fixed)
        1    0.045    0.045    0.045    0.045 {built-in technique numpy.arange}
        1    0.000    0.000    0.014    0.014 <__array_function__ internals>:177(sum)

Das ist ein fantastisches Ergebnis, das die Leistungsfähigkeit der Profilerstellung demonstriert. Wir haben unsere Mühe auf die Teile des Codes gelenkt, die wichtig waren. Der Gründlichkeit halber habe ich auch Snakeviz für das korrigierte Skript ausgeführt.

%%snakeviz
run_all_systems()
Bild vom Autor

Die bemerkenswerteste Änderung ist die Reduzierung der Gesamtlaufzeit von etwa 30 Sekunden auf etwa 6 Sekunden. Dies ist eine 5-fache Beschleunigung, die durch die Beseitigung der drei Hauptengpässe erreicht wird, die im „Vorher“-Profil sichtbar waren.

Schauen wir uns jedes einzeln an.

1. Die iteration_heavy_task

Vorher (Das Drawback)
Im ersten Bild ist der große Balken auf der linken Seite, iteration_heavy_task, der größte Engpass, der verbraucht 14,3 Sekunden.

  • Warum conflict es langsam? Diese Aufgabe conflict ein klassischer „Tod durch tausend Schnitte“. Die Funktion simulieren_tiny_op hat quick nichts bewirkt, wurde aber millionenfach innerhalb einer reinen Python-for-Schleife aufgerufen. Der immense Aufwand, den der Python-Interpreter mit dem wiederholten Starten und Stoppen eines Funktionsaufrufs verursacht, conflict die alleinige Ursache für die Langsamkeit.

Die Lösung
Die feste Model iteration_heavy_task_fixed erkannte, dass das Ziel ohne die Schleife erreicht werden konnte. In unserem Fall bedeutete dies, die sinnlose Schleife vollständig zu entfernen. In einer realen Anwendung müsste dazu eine einzige „Massen“-Operation gefunden werden, um die iterative Operation zu ersetzen.

Nachher (Das Ergebnis)
Im zweiten Bild ist die iteration_heavy_task-Leiste zu sehen völlig verschwunden. Es ist jetzt so schnell, dass seine Laufzeit nur einen winzigen Bruchteil einer Sekunde beträgt und auf dem Diagramm unsichtbar ist. Wir haben ein 14,3-Sekunden-Drawback erfolgreich behoben.

2. Die cpu_heavy_task

Vorher (Das Drawback)
Der zweite große Engpass, deutlich sichtbar als großer orangefarbener Balken rechts, ist cpu_heavy_task, der in Anspruch genommen wurde 12,9 Sekunden.

  • Warum conflict es langsam? Wie die Iterationsaufgabe conflict auch diese Funktion durch die Geschwindigkeit der Python-for-Schleife begrenzt. Während die darin enthaltenen mathematischen Operationen schnell waren, musste der Interpreter jede der Millionen Berechnungen einzeln verarbeiten, was für numerische Aufgaben äußerst ineffizient ist.

Die Lösung
Die Lösung conflict Vektorisierung Verwendung der NumPy-Bibliothek. Anstatt eine Python-Schleife zu verwenden, hat cpu_heavy_task_fixed ein NumPy-Array erstellt und alle mathematischen Operationen (np.sqrt, np.sin usw.) gleichzeitig für das gesamte Array ausgeführt. Diese Operationen werden in hochoptimiertem, vorkompiliertem C-Code ausgeführt, wobei die langsame Python-Interpreterschleife vollständig umgangen wird.

Nachher (Das Ergebnis).
Genau wie der erste Engpass ist auch die cpu_heavy_task-Leiste aus dem „Nachher“-Diagramm verschwunden. Seine Laufzeit wurde von 12,9 Sekunden auf wenige Millisekunden reduziert.

3. Die „memory_heavy_string_task“.

Vorher (Das Drawback):
Im ersten Diagramm lief die speicherintensive_string_task, aber ihre Laufzeit conflict im Vergleich zu den beiden anderen größeren Problemen gering, sodass sie in den kleinen, unbeschrifteten Bereich ganz rechts verbannt wurde. Es conflict ein relativ kleines Drawback.

Die Lösung
Die Lösung für diese Aufgabe bestand darin, das Ineffiziente zu ersetzen melden += „…” String-Verkettung mit einer viel effizienteren Methode: Erstellen einer Liste aller String-Teile und anschließender Aufruf „“.verbinden() ein einziges Mal am Ende.

Nachher (Das Ergebnis)
Im zweiten Diagramm sehen wir das Ergebnis unseres Erfolgs. Nachdem die beiden Engpässe von mehr als 10 Sekunden beseitigt wurden, ist nun die speicherintensive Zeichenfolgenaufgabe behoben neuer dominanter EngpassBuchhaltung 4,34 Sekunden der gesamten 5,22 Sekunden Laufzeit.

Snakeviz lässt uns sogar einen Blick in diese feste Funktion werfen. Der neue wichtigste Beitragszahler ist der orangefarbene Balken mit der Bezeichnung (Listenverständnis), was dauert 3,52 Sekunden. Dies deutet darauf hin, dass auch in der behoben Der zeitaufwändigste Teil des Codes besteht nun darin, die umfangreiche Liste der Zeichenfolgen im Speicher zu erstellen, bevor sie verknüpft werden können.

Zusammenfassung

Dieser Artikel bietet eine praktische Anleitung zum Identifizieren und Beheben von Leistungsproblemen in Python-Code und argumentiert, dass Entwickler dazu Profilierungstools verwenden sollten messen Leistung zu verbessern, anstatt sich auf Instinct oder Vermutungen zu verlassen, um die Ursache von Verlangsamungen zu ermitteln.

Ich habe einen methodischen Arbeitsablauf mit zwei Schlüsselwerkzeugen demonstriert:-

  • cProfile: Pythons integrierter Profiler, der zum Sammeln detaillierter Daten zu Funktionsaufrufen und Ausführungszeiten verwendet wird.
  • Schlangenviz: Ein Visualisierungstool, das die Daten von cProfile in ein interaktives „Eiszapfen“-Diagramm umwandelt und es so einfach macht, visuell zu erkennen, welche Teile des Codes die meiste Zeit verbrauchen.

Der Artikel verwendet eine Fallstudie eines absichtlich langsamen Skripts, das mit drei deutlichen und erheblichen Engpässen entwickelt wurde:

  1. Eine iterationsgebundene Aufgabe: Eine Funktion, die millionenfach in einer Schleife aufgerufen wird und die Leistungseinbußen des Funktionsaufruf-Overheads von Python („Tod durch tausend Schnitte“) zeigt.
  2. Eine CPU-gebundene Aufgabe: Eine for-Schleife, die Millionen mathematischer Berechnungen durchführt und die Ineffizienz von reinem Python für schwere numerische Arbeiten verdeutlicht.
  3. Eine speichergebundene Aufgabe: Eine große Zeichenfolge, die durch wiederholte +=-Verkettung ineffizient erstellt wurde.

Durch die Analyse der Snakeviz-Ausgabe habe ich diese drei Probleme lokalisiert und gezielte Korrekturen vorgenommen.

  • Der Iterationsengpass wurde behoben durch Eliminierung der unnötigen Schleife.
  • Der CPU-Engpass wurde durch Vektorisierung mit NumPy behoben, das mathematische Operationen in schnellem, kompiliertem C-Code ausführt.
  • Der Speicherengpass wurde durch das Anhängen von Zeichenfolgenteilen an eine Liste und die Verwendung eines einzigen, effizienten „“ behoben..verbinden() Anruf.

Diese Korrekturen führten zu einer erheblichen Beschleunigung und verkürzten die Laufzeit des Skripts erheblich 30 Sekunden bis knapp vorbei 6 Sekunden. Abschließend habe ich gezeigt, dass der Profiler auch nach der Lösung größerer Probleme erneut zur Identifizierung verwendet werden kann neukleinere Engpässe, was verdeutlicht, dass die Leistungsoptimierung ein iterativer Prozess ist, der durch Messungen gesteuert wird.

[ad_2]

]]>
http://chatgpz.de/machine-learning/denken-sie-dass-ihr-python-code-langsam-ist-hoeren-sie-auf-zu-raten-und-beginnen-sie-mit-dem-messen/feed/ 0 15143