Optimierung einer Funktion

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Guten Morgen,

ich stehe erstmals vor dem Problem, das ich eine Funktion habe, deren Ablauf mir zu langsam ist. Bisher habe ich immer gesagt: hey, wenn es funktioniert ist es genug - die Laufzeit ist erst mein zweites Problem. Erstmals ist jetzt der Fall eingetroffen, dass ich mit der Laufzeit ein Problem kriege.

Die Funktion ruft sich selbst auf und greift über das Django-ORM auf eine Datenbank zu. Ich konnte schon eine Menge weg optimieren, indem ich Resultate von Abfragen selber in einen Buffer stopfe und so eine erneute Abfrage umgehe. Da sich die Funktion rekursiv aufruft hat das eine Menge gebracht.

timeit kenne ich, damit kann ich die Laufzeit einer Funktion prüfen und schauen was Optimierungen gebracht haben. Gibt es auch eine Möglichkeit über einen Debugger oder ein ähnliches Tool nachzuvollziehen wofür in der Funktion gerade am meisten Zeit flöten geht? Also in welcher Zeile er am meisten verharrt?

Gruß
Sparrow
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Das Stichwort lautet "Profiler". line_profiler könnte in diesem Fall recht genau das sein, was du suchst.
Das Leben ist wie ein Tennisball.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Super, das sieht gut aus, danke!

Jetzt muss ich das nur noch so hin bekommen, dass es auch mit Django in der manage.py shell funktioniert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@sparrow:
Falls es am ORM und der Datenstruktur liegt - evtl. kann ein raw-SQL weiterhelfen. (so ins Blaue getippt, ohne nähere Angaben kann man wenig dazu sagen)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Evtl. kann man auch auf DB-Ebene etwas mit Indizes beschleunigen - auch das ins Blaue getippt :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Ich glaube so einfach wie ich mir das vorgestellt habe, geht es schlicht nicht.

Es gibt im Prinzip zwei Klassen: Artikel und Charge.
Artikel bekommen Chargen, wenn man verschiedene Artikel miteinander kombiniert ergeben die eine neue Charge.
Artikel haben außerdem Merkmale (ManyToMany)
Merkmale können entweder vererbt werden, unter bestimmten Voraussetzungen vererbt werden oder nie vererbt werden.

In Charge steckt eine Funktion, die es erlaubt die aktuellen Merkmale abzufragen. Dafür läuft die alle Ursprungschargen durch und holt über eben diese Funktion deren Merkmale ab. So weit bis es keine Ursprungscharge gibt, dann zieht es die Merkmale der Artikel.

Angenommen man hat 3 Artikel mit dem Merkmal "rein Edelstahl" (Chargen 1, 2, 3) und einen ohne (Charge 4). "rein Edelstahl" wird aber nur vererbt, wenn es alle Komponenten haben. 1+2+3+4 werden zu Charge 5 verarbeitet.

Ruft man jetzt für Charge 5 die Merkmale ab, schaut er nacheinander in die Ursprungschargen (1, 2, 3, 4) und bemerkt, dass "rein Edelstahl" nicht in allen davon enthalten ist, also wird sie für Charge 5 auch nicht ausgegeben.

Momentan habe ich hier halt das Problem mit 109 Ursprungschargen, und da dauert das Nachschauen halt etwas über 20 Sekunden. Ich glaube, das ist eher ein Designproblem. Ich muss halt im Vorfeld für jede der Ursprungschargen bestimmen welche Materialien darin sind, und das sind jede Menge Zugriffe auf die Datenbank und dauert.

Ich überlege, ob es nicht sinnvoll wäre die Information in die Datenbank zu legen, statt sie erst zu generieren wenn ich sie brauche. Also immer dann, wenn sich die Konstellation ändert, einmal die aktuellen Merkmale herausfinden und speichern.
Dami123
User
Beiträge: 225
Registriert: Samstag 23. Februar 2013, 13:01

Der Artikel http://pypix.com/tools-and-tips/developer-tools/ beschriebt einige nützliche Funktionen, die du ggf. verwenden könntest.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@sparrow:
Kommt das DB-Design von Dir oder ist es gegeben und Du musst diese Funktion nachrüsten?

Wenn ich das richtig verstehe, hast Du das Problem eines Vererbungsbaumes (den Chargen), welche OneToMany in einer Tabelle liegen (eine neue Kindcharge ergibt sich aus möglichen Kombinationen von verschiedenen Elternchargen). In einer relationalen DB sind Hierachien immer problematisch, man kann sie aber teilweise für die kritischen (häufigen) use cases optimieren.

So könntest Du z.B. "Schattenartikel" für neue Kindchargen aus mehreren Elternchargen erstellen, welche die Merkmale auf die neue Charge abbilden. Damit wird das INSERT für eine neue Charge etwas teurer, die Abfrage dafür billig, da Du das Traversieren durch alle Chargenvorfahren sparst. Ein Problem, was hierbei auftauchen könnte, sind die Merkmalskombinationen und die Frage, wie Du diese in der DB ablegst. Wenn die Merkmale frei setzbar sein sollen, musst Du von Spalten auf Zeilen wechseln (Problem der dünn besetzten Matrix --> siehe EAV).

Mit einer derartigen Umgstaltung der DB sollte die Frage "Welche Merkmale hat diese Charge?" mit einem Query beantwortbar sein.

NB: Mir sind Deine Begrifflichkeiten nicht ganz klar. Ich benutze oben "Charge" als Prototypen mit Merkmalen für einen Artikel bzw. ein Artikelset, der/das dann die entsprechenden Ausprägungen hat. Kann es sein, dass Du das nicht sauber trennst? Normalerweise steht eine Charge ja für eine gewisse Ausprägung (z.B. bestimmter Artikel eines bestimmten Zeitraumes). Das weitergedacht, kann es eigentlich keine Chargenkombinationen mit Merkmalswechsel geben, da diese an einen Artikel gebunden sind. Hier ist es vllt. sinnvoll, die Kombinationsmöglichkeiten eher über Artikel zu spannen (Kombiartikel), welche dann wiederum Chargen bekommen.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Das Datenbankdesign kommt von mir selbst, bzw. wurde die konkrete Umsetzung durch das Django-ORM vorgenommen.

Das Problem hast du richtig erkannt, ich konnte das inzwischen auch so weit herunter brechen, dass ich mir sicher bin, dass es an den vielen Abfragen an die Datenbank liegt. Danke für deine Tipps, ich werde mir das einmal anschauen.

Die Trennung zwischen Charge und Artikel besteht schon.
Chargen werden gebildet indem Artikel (mit den Merkmalen) oder Chargen zusammen geführt werden. Die Charge repräsentiert also den Status dieser Zusammenführung zu diesem Zeitpunkt. Da die "Zutaten" aber variabel sind, und auch nachträglich noch etwas zugefügt werden könnte, kann ich das nicht an einen resultierenden Artikel statisch fest machen.

Ich arbeite im Moment daran, die Merkmale in der Charge zu speichern und bei einer Änderung einmal den Baum abzuklappern und die Merkmale entsprechend zu speichern. Teuer beim speichern, dafür ist das lesen dann sehr schnell.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@sparrow: Kann es sein, dass du mit Charge eigentlich Stückliste meinst? Falls das so ist, wirf einen Blick auf das hier.

Das Problem, das du beschreibst, erfordert eine höherstufige Logik, bei der man über Prädikate ("Merkmale") quantifizieren kann. SQL kann das nur in sehr begrenztem Maße unter Zuhilfenahme von Funktionen wie count(), min(), max(), sum() und dergleichen, denn die relationale Algebra, auf der SQL beruht, ist bloß äquivalent zur Prädikatenlogik erster Stufe.

Wenn du eine ordentliches modernes DBMS verwendest (nein, MySQL und SQLite fallen nicht unter diese Kategorie), könntest du eine Abfrage mittels einer rekursiven Abfrage formulieren (mit passenden Indizes natürlich). Dabei fragst du zunächst nur die transitive Hülle über den Artikeln ohne die Merkmale ab. Die Ergebnismenge kannst du dann mit der Merkmale-Tabelle joinen. Für Merkmale wie "rein Edelstahl" muss der Join allerdings mittels so etwas wie "WHERE ALL IN (SELECT ...)" ausgeführt werden. Die genaue Syntax habe ich vergessen, mein SQL Fu nach Jahren der Abstinenz nicht mehr so groß ist, wie es mal war.

Ich vermute, dass es dadurch schneller wird, aber garantieren kann ich es nicht. Einen Versuch ist es allemal wert.

Oder du kannst, wie du selber schon vorgeschlagen hast, einfach denormalisieren. Für Konsistenz musst du dann aber selber sorgen.
In specifications, Murphy's Law supersedes Ohm's.
Antworten