Seite 1 von 1

Performance Problem

Verfasst: Mittwoch 18. März 2026, 17:32
von Dennis89
Hallo zusammen,

ich benötige mal wieder eure Hilfe. Mit Performance habe ich mich noch nie intensiv beschäftigt, da das für mich noch nie ein Problem war. Jetzt habe ich aber einen Code erhalten, der ist sehr langsam. Mit messen habe ich dann herausgefunden, dass ein Problem darin liegt, dass Ergebnisse mit `append` ein er Liste hinzugefügt wurden. Die Messung hat auch gezeigt, dass mit steigender Listenlänge, die Zeit hierfür auch anstieg. Hier wird ja nichts angehängt, sondern jedes mal eine neue Liste erzeugt, richtig? Da der Zugriff bzw. das raussuchen bestimmter Ergebnisse mittels Indexzugriff auch noch Zeit beanspruchte, habe ich mir überlegt, was ich tun kann um das zu ändern.
Mit `pd.DataFrame` habe ich schöne Optionen und habe damit angefangen. Mein Problem ist, dass das nicht schneller ist.
Ich habe ein Minimalbeispiel geschrieben, die gewünschte Logik müsste darin gut erkennbar sein. Die Berechnung über die Letzten 3 Reihen ist wichtig. Für eine Darstellung im Diagramm benötige ich aber alle Reihen, daher kann ich die vorherigen nicht einfach verwerfen. Ziel ist, dass ich `results` , `mass`, `volume` und `general_results` in einem Objekt habe und dann je nach dem die gewünschten Werte ausgeben kann oder Diagramme erstellen.

Wie macht man so etwas schneller, wenn man die Berechnung selbst nicht ändern kann? Wie und wo speichere ich die Daten sinnvoll und schnell zwischen? In eine Datei schreiben? Bin schon über `h5py` gestolpert. `pandas` hatte für mich den Vorteil mit `groupy` weil ich dann so schön die Berechnung jeder Stufe abfragen kann und keine Schleife benötige. `groupy` sei laut StackOverflow allerdings nicht so schnell. Für mich bin ich jetzt nach sehr vielen Stunden des Code-Uschreibens an einem Punkt, an dem ich sehr dankbar über einen Wegweiser wäre.

Code: Alles auswählen

#!/usr/bin/env python
import dataclasses

import pandas as pd



@dataclasses.dataclass
class State:
    volume: float = 0
    mass: float = 0
    stage: int = 0


def get_important_results(results):
    last_rows = results.tail(3)
    mass = last_rows.groupby("stage")["mass"].sum()
    volume = last_rows.groupby("stage")["volume"].sum()
    return mass, volume


def main():
    # In reality there are many more values, as shown here
    states = [State() for _ in range(3)]
    result = pd.DataFrame([dataclasses.asdict(states[0])])
    for value in range(10):
        for stage, state in enumerate(states, 1):
            # Placeholder for calculating
            state.stage = stage
            state.volume = 300 + value * stage
            state.mass = 200 + value * stage
            result = pd.concat(
                [result, pd.DataFrame([dataclasses.asdict(state)])], ignore_index=True
            )
    mass, volume = get_important_results(result)
    general_results = pd.DataFrame([{"something": 123}])
    print(mass)
    print(mass[2])


if __name__ == "__main__":
    main()
Danke vorab und viele Grüße
Dennis

Edit: Auch die `dataclass` kann ich rauswerfen/ersetzen, die ist drin, weil sie halt drin war und ich noch nicht weiß ob das Sinn macht oder nicht.

Re: Performance Problem

Verfasst: Mittwoch 18. März 2026, 18:17
von __blackjack__
@Dennis89: `list.append()` erzeugt nicht jedes mal eine neue Liste, da wäre die Laufzeit unterirdisch. Das ist armotisiert O(1), das heisst es ist in der Regel eine sehr schnelle Operation, aber ab und zu muss der interne Speicher der Liste wachsen und dann muss gegebenfalls etwas umkopiert werden.@Dennis89: `list.append()` erzeugt nicht jedes mal eine neue Liste, da wäre die Laufzeit unterirdisch. Das ist armotisiert O(1), das heisst es ist in der Regel eine sehr schnelle Operation, aber ab und zu muss der interne Speicher der Liste wachsen und dann muss gegebenfalls etwas umkopiert werden.

Das mit dem `pd.concat()` ist langsam, weil da tatsächlich jedes mal ein neuer Dataframe in jedem Schleifendurchlauf erstellt wird. Da würde man die Daten erst in einer Liste speichern und dann zum Schluss das `Dataframe`-Objekt erstellen.

Was mir daneben so auf Anhieb auffällt ist das ``last_rows.groupby("stage")`` zweimal direkt hintereinander mit dem gleichen Ergebnis gemacht wird.

Kurzzeitiges Zwischenspeichern kann man mit `to_pickle()` machen. Sonst würde ich Parquet oder Feather als Speicherformat verwenden.

Das mit dem `pd.concat()` ist langsam, weil da tatsächlich jedes mal ein neuer Dataframe in jedem Schleifendurchlauf erstellt wird. Da würde man die Daten erst in einer Liste speichern und dann zum Schluss das `Dataframe`-Objekt erstellen.

Was mir daneben so auf Anhieb auffällt ist das ``last_rows.groupby("stage")`` zweimal direkt hintereinander mit dem gleichen Ergebnis gemacht wird.

Kurzzeitiges Zwischenspeichern kann man mit `to_pickle()` machen. Sonst würde ich Parquet oder Feather als Speicherformat verwenden.

Re: Performance Problem

Verfasst: Mittwoch 18. März 2026, 19:09
von Dennis89
Danke für die schnelle Antwort.
Du meinst ich hätte `last_rows.groupby("stage")[["mass", "volume"]].sum()` schreiben können um mir einen `groupby`-Aufruf zu sparen?

Ich teste mal die Variante, mit allem in eine Liste schreiben und dann ein `DataFrame` zu erzeugen und sollte dass nicht reichen, lese ich mich in die anderen 3 Optionen ein.

Grüße
Dennis

Re: Performance Problem

Verfasst: Donnerstag 19. März 2026, 16:04
von Dennis89
Hallo zusammen,

nun muss ich das angehen, vor dem ich mich gedrückt habe, weil ich keine sinnvolle Lösung gefunden habe und das die größte Bremse ist.
Ich habe ein Konstrukt, dass soooooo *ähnlich* aussieht:

Code: Alles auswählen

class Results:
    def __init__(self):
        self.xy = []

    def __repr__(self):
        return f"Tadaa: {self.xy!r}"

def calculate(result, a, b):
    result.xy.append(a + b)


def main():
    values = [(1, 2), (3, 4), (5, 6)]
    results = [Results() for _ in values]
    for _ in range(10):
        for result, (a, b) in zip(results, values):
            calculate(result, a, b)
    for result in results:
        print(result)

if __name__ == '__main__':
    main()
Die drei Berechnungen in der zweiten `for`- Schleife könnten "gleichzeitig" gemacht werden, daher habe ich sowas probiert:

Code: Alles auswählen

from threading import Thread

class Results:
    def __init__(self):
        self.xy = []

    def __repr__(self):
        return f"Tadaa: {self.xy!r}"

def calculate(result, a, b):
    result.xy.append(a + b)


def main():
    values = [(1, 2), (3, 4), (5, 6)]
    results = [Results() for _ in values]
    for _ in range(10):
        stages = [(result, a, b) for result, (a, b) in zip(results, values)]
        thread = Thread(target=calculate, args=stages[0])
        thread.start()
        thread.join()
        calculate(*stages[1])
        calculate(*stages[2])
    for result in results:
        print(result)

if __name__ == '__main__':
    main()
Das auch mit zwei Threads und nur eine Berechnung im Hauptthread, allerdings ist das nicht merkbar schneller.

Habt ihr eine Idee, wie ich die innere Schleife schneller abgearbeitet bekomme? Die äußere Schleife muss in meinem Fall ca. 6000 mal durchlaufen, weil es da mathematische Model dahinter so benötigt und darauf habe ich keinen Einfluss. Ich habe ja in einem anderen Thread schon mal nach der Einbindung von Rust in Python gefragt, weil ich wusste das so ein Problem auf mich zu kommt. Allerdings denke ich, dass mein Problem mit Python und richtigen Programmierkenntnissen noch deutlich optimierbar ist und genau dass ist ja das was ich lernen will.

Danke und Grüße
Dennis

Re: Performance Problem

Verfasst: Donnerstag 19. März 2026, 16:45
von __blackjack__
Das mit dem Thread macht so keinen Sinn. Für das erste `stage`-Element wird ein Thread gestartet, der aber nicht mal theoretisch zu irgend etwas anderem parallel läuft, weil nach dem `start()` ja erst einmal mit `join()` gewartet wird bis der fertig ist. Das erzeugt an der Stelle einfach nur zusätzliche, überflüssige Mehrarbeit.

In CPython steht zudem noch das „global interpreter lock“ einer parallelen Verarbeitung von reinem Python-Code in Threads im Weg. Aber selbst wenn dem nicht so wäre: auch in C würde man keinen Thread für eine so popelig kleine ”Rechnung” starten, denn das Thread starten und danach wieder aufräumen braucht ja auch Zeit.

Multiprocessing wäre in CPython der Weg um parallel zu rechnen. Da ist das Hochziehen und Aufräumen aber _noch_ teurer als bei Theads. Dazu kommt dann auch noch die Kommunikation der Argumente und Ergebnisse zwischen den Prozessen.

Re: Performance Problem

Verfasst: Donnerstag 19. März 2026, 17:49
von Sirius3
@Dennis89: Performance-Probleme zu diagnostizieren ist schwierig, wenn Du nicht den wirklichen Code zeigst.
Hier würde ich sagen, Du hast zwei Vektoren mit Zahlen, also nimmt man numpy und hat kein Geschwindigkeitsproblem.
Wahrscheinlich ist das Problem irgendwo anders.

Threads / Multiprocessing können helfen, aber das ist der letzte Schritt, wenn alles andere schon optimal läuft. Dann benutzt man aber sowas wie concurrent.futures und startet keine Prozesse von Hand.

Re: Performance Problem

Verfasst: Donnerstag 19. März 2026, 19:15
von Dennis89
Danke für eure Antworten.
Den originalen Code kann ich nicht öffentlich zeigen, der gehört nicht mir und enthält firmeninternes. Eine gekürzte Version könnte ich per PN zukommen lassen. Das ist dann aber keine Forums-Philisophie mehr.
Ich vermute dass der `numpy`- Ansatz funktioniert, allerdings sehe ich noch nicht wie, da der Code schon "wild" ist und jede Änderung Auswirkung auf "vorne" und "hinten" hat. Das ist super nervig, weil ich nach jeder Änderung den alten und den neuen Code laufen lassen muss um zu sehen, ob die Ergebnisse noch stimmen und die ständigen ~200 Sekunden warten, sind nicht so cool.

Grüße
Dennis

Re: Performance Problem

Verfasst: Donnerstag 19. März 2026, 22:21
von sparrow
@Dennis89: Deshalb hat man kleine Funktionen und dafür Tests.