Arbeiten Sie in Python nach dem Prinzip der einmaligen Verantwortung (Single Responsibility Principle, SRP), wenn Anrufe teuer sind

12

Einige Basispunkte:

  • Python-Methodenaufrufe sind aufgrund ihrer Interpretation "teuer" . Wenn Ihr Code einfach genug ist, hat das Auflösen von Python-Code theoretisch negative Auswirkungen, abgesehen von der Lesbarkeit und Wiederverwendung ( ein großer Gewinn für Entwickler, nicht so sehr für Benutzer ).
  • Das Single-Responsibility-Prinzip (SRP) hält den Code lesbar und ist einfacher zu testen und zu warten.
  • Das Projekt hat einen besonderen Hintergrund, in dem lesbarer Code, Tests und Zeitleistung gewünscht werden .

Beispielsweise ist ein Code wie dieser, der mehrere Methoden (x4) aufruft, langsamer als der folgende, der nur eine ist.

from operator import add

class Vector:
    def __init__(self,list_of_3):
        self.coordinates = list_of_3

    def move(self,movement):
        self.coordinates = list( map(add, self.coordinates, movement))
        return self.coordinates

    def revert(self):
        self.coordinates = self.coordinates[::-1]
        return self.coordinates

    def get_coordinates(self):
        return self.coordinates

## Operation with one vector
vec3 = Vector([1,2,3])
vec3.move([1,1,1])
vec3.revert()
vec3.get_coordinates()

Im Vergleich dazu:

from operator import add

def move_and_revert_and_return(vector,movement):
    return list( map(add, vector, movement) )[::-1]

move_and_revert_and_return([1,2,3],[1,1,1])

Wenn ich so etwas parallelisieren will, ist es ziemlich objektiv, dass ich an Leistung verliere. Denken Sie daran, das ist nur ein Beispiel; In meinem Projekt gibt es mehrere Mini-Routinen mit Mathematik wie folgt: - Obwohl es viel einfacher ist, damit zu arbeiten, mögen es unsere Profiler nicht.


Wie und wo setzen wir die SRP ein, ohne die Leistung in Python zu beeinträchtigen, da sich ihre inhärente Implementierung direkt auf sie auswirkt?

Gibt es Workarounds wie eine Art Pre-Prozessor, der die Dinge für die Veröffentlichung vorbereitet?

Oder ist Python einfach schlecht darin, die Code-Aufschlüsselung insgesamt zu handhaben?

lucasgcb
quelle
4
Mögliches Duplikat von Ist die Mikrooptimierung beim Codieren wichtig?
Mücke
19
Ihre beiden Codebeispiele unterscheiden sich nicht in der Anzahl der Verantwortlichkeiten. Die SRP ist keine Methodenzählübung.
Robert Harvey
2
@RobertHarvey Du hast Recht, entschuldige das schlechte Beispiel und ich bearbeite ein besseres, wenn ich Zeit habe. In beiden Fällen leidet die Lesbarkeit und Wartbarkeit und schließlich bricht die SRP innerhalb der Codebasis zusammen, wenn wir Klassen und ihre Methoden reduzieren.
Lucasgcb
4
Beachten Sie, dass Funktionsaufrufe in jeder Sprache teuer sind , obwohl AOT-Compiler den Luxus von Inlining haben
Eevee
6
Verwenden Sie eine JITted-Implementierung von Python wie PyPy. Sollte dieses Problem meistens beheben.
Bakuriu

Antworten:

17

ist Python einfach schlecht im Umgang mit Code-Aufschlüsselung insgesamt?

Leider ist Python langsam, und es gibt viele Anekdoten darüber, wie Menschen die Leistung drastisch steigern, indem sie Funktionen einfügen und ihren Code hässlich machen.

Es gibt ein Workaround, Cython, das eine kompilierte Version von Python und viel schneller ist.

--Bearbeiten Ich wollte nur einige der Kommentare und andere Antworten ansprechen. Obwohl der Schub von ihnen nicht vielleicht pythonspezifisch ist. aber allgemeinere Optimierung.

  1. Optimieren Sie erst, wenn Sie ein Problem haben, und suchen Sie dann nach Engpässen

    Generell guter Rat. Die Annahme ist jedoch, dass 'normaler' Code normalerweise performant ist. Dies ist nicht immer der Fall. Einzelne Sprachen und Frameworks haben jeweils ihre eigenen Besonderheiten. In diesem Fall Funktionsaufrufe.

  2. Es ist nur ein paar Millisekunden, andere Dinge werden langsamer sein

    Wenn Sie Ihren Code auf einem leistungsstarken Desktop-Computer ausführen, ist es Ihnen wahrscheinlich egal, solange Ihr Einzelbenutzercode in wenigen Sekunden ausgeführt wird.

    Geschäftscode wird jedoch in der Regel für mehrere Benutzer ausgeführt und erfordert mehr als einen Computer, um die Last zu unterstützen. Wenn Ihr Code doppelt so schnell ausgeführt wird, bedeutet dies, dass Sie doppelt so viele Benutzer oder halb so viele Computer haben können.

    Wenn Sie Eigentümer Ihrer Maschinen und Ihres Rechenzentrums sind, haben Sie im Allgemeinen einen hohen Overhead bei der CPU-Leistung. Wenn Ihr Code etwas langsam läuft, können Sie ihn absorbieren, zumindest bis Sie eine zweite Maschine kaufen müssen.

    In Zeiten des Cloud Computing, in denen Sie nur genau die Rechenleistung verwenden, die Sie benötigen, und nicht mehr, fallen direkte Kosten für nicht performanten Code an.

    Durch die Verbesserung der Leistung können die Hauptkosten für ein Cloud-basiertes Unternehmen drastisch gesenkt werden, und die Leistung sollte im Vordergrund stehen.

Ewan
quelle
1
Während Roberts Antwort dabei hilft, einige Gründe für mögliche Missverständnisse bei der Durchführung dieser Art von Optimierung (die zu dieser Frage passt ) zu decken , wird die Situation meines Erachtens etwas direkter und im Einklang mit dem Python-Kontext beantwortet.
Lucasgcb
2
Entschuldigung, es ist etwas kurz. Ich habe keine Zeit mehr zu schreiben. Aber ich denke, Robert ist in dieser Sache falsch. Der beste Rat mit Python scheint zu sein, sich während des Programmierens zu profilieren . Nehmen Sie nicht an, dass es performant ist und optimieren Sie es nur, wenn Sie ein Problem finden
Ewan
2
@Ewan: Sie müssen nicht zuerst das gesamte Programm schreiben, um meinen Rat zu befolgen. Eine oder zwei Methoden sind mehr als ausreichend, um ein angemessenes Profil zu erhalten.
Robert Harvey
1
Sie können auch pypy ausprobieren, ein JITted-Python
Eevee
2
@Ewan Wenn Sie sich wirklich Sorgen über den Performance-Overhead von Funktionsaufrufen machen, ist das, was Sie tun, wahrscheinlich nicht für Python geeignet. Aber dann kann ich mir wirklich nicht viele Beispiele vorstellen. Die überwiegende Mehrheit des Geschäftscodes ist auf E / A beschränkt, und die CPU-Belastung wird normalerweise durch Aufrufen nativer Bibliotheken (Numpy, Tensorflow usw.) bewältigt.
Voo
50

Viele mögliche Leistungsprobleme sind in der Praxis kein wirkliches Problem. Das Problem, das Sie ansprechen, kann eines davon sein. In der Umgangssprache bezeichnen wir das Sorgen um diese Probleme als vorzeitige Optimierung, ohne den Beweis zu erbringen, dass es sich tatsächlich um Probleme handelt .

Wenn Sie ein Front-End für einen Webdienst schreiben, wird Ihre Leistung durch Funktionsaufrufe nicht wesentlich beeinträchtigt, da die Kosten für das Senden von Daten über ein Netzwerk die Zeit für einen Methodenaufruf bei weitem überschreiten.

Wenn Sie eine enge Schleife schreiben, die sechzig Mal pro Sekunde einen Videobildschirm aktualisiert, ist dies möglicherweise von Bedeutung. Ich behaupte jedoch, dass Sie größere Probleme haben, wenn Sie versuchen, dies mit Python zu tun, ein Job, für den Python wahrscheinlich nicht geeignet ist.

Wie immer ist die Art und Weise, wie Sie herausfinden, zu messen. Führen Sie einen Leistungsprofiler oder einige Zeitgeber über Ihren Code aus. Sehen Sie, ob es in der Praxis ein echtes Problem ist.


Das Prinzip der einheitlichen Verantwortung ist kein Gesetz oder Auftrag. Es ist eine Richtlinie oder ein Prinzip. Beim Softwaredesign geht es immer um Kompromisse. es gibt keine absoluten. Es ist nicht ungewöhnlich, die Lesbarkeit und / oder Wartbarkeit für die Geschwindigkeit abzuwägen, sodass Sie möglicherweise SRP auf dem Altar der Leistung opfern müssen. Aber machen Sie diesen Kompromiss erst, wenn Sie wissen, dass Sie ein Leistungsproblem haben.

Robert Harvey
quelle
3
Ich denke, das stimmte, bis wir das Cloud Computing erfunden haben. Jetzt kostet eine der beiden Funktionen viermal so viel wie die andere
Ewan
2
@Ewan 4 Mal spielt möglicherweise keine Rolle, bis Sie gemessen haben, dass es signifikant genug ist, um sich darum zu kümmern. Wenn Foo 1 ms dauert und Bar 4 ms dauert, ist das nicht gut. Bis Sie feststellen, dass die Übertragung der Daten über das Netzwerk 200 ms dauert. Zu diesem Zeitpunkt ist es nicht so wichtig, dass Bar langsamer ist. (Nur ein mögliches Beispiel dafür, wo es keinen spürbaren oder wirkungsvollen Unterschied macht, wenn man X-mal langsamer ist, was nicht unbedingt super realistisch sein soll.)
Becuzz,
8
@Ewan Wenn Sie durch die Reduzierung der Rechnung 15 US-Dollar pro Monat einsparen, die Reparatur und der Test eines Auftragnehmers für 125 US-Dollar pro Stunde jedoch 4 Stunden in Anspruch nehmen, kann ich leicht begründen, dass sich die Zeit eines Unternehmens nicht lohnt (oder zumindest nicht richtig handelt) jetzt, wenn die Markteinführung entscheidend ist, usw.). Es gibt immer Kompromisse. Und was unter bestimmten Umständen sinnvoll ist, mag unter anderen Umständen nicht sinnvoll sein.
Becuzz
3
Ihre AWS Rechnungen sind in der Tat sehr niedrig
Ewan
6
@Ewan AWS wird ohnehin chargenweise an die Decke gerundet (Standard sind 100 ms). Das bedeutet, dass Sie durch diese Art der Optimierung nur dann etwas sparen, wenn Sie nicht ständig zum nächsten Block weitergeleitet werden.
Delioth
2

Zunächst einige Erläuterungen: Python ist eine Sprache. Es gibt verschiedene Interpreter, die in der Python-Sprache geschriebenen Code ausführen können. Auf die Referenzimplementierung (CPython) wird normalerweise verwiesen, wenn von "Python" die Rede ist, als ob es sich um eine Implementierung handelt. Es ist jedoch wichtig, genau zu sein, wenn es um Leistungsmerkmale geht, da sie sich zwischen den Implementierungen stark unterscheiden können.

Wie und wo setzen wir die SRP ein, ohne die Leistung in Python zu beeinträchtigen, da sich ihre inhärente Implementierung direkt auf sie auswirkt?

Fall 1.) Wenn Sie reinen Python-Code haben (<= Python Language Version 3.5, 3.6 unterstützt Beta-Level), der nur auf reinen Python-Modulen basiert, können Sie SRP überall verwenden und PyPy zum Ausführen verwenden. PyPy ( https://morepypy.blogspot.com/2019/03/pypy-v71-released-now-uses-utf-8.html ) ist ein Python-Interpreter, der über einen Just-in-Time-Compiler (JIT) verfügt und Funktionen entfernen kann Rufen Sie den Overhead auf, solange er genügend Zeit zum "Aufwärmen" hat, indem Sie den ausgeführten Code verfolgen (einige Sekunden IIRC). **

Wenn Sie nur den CPython-Interpreter verwenden dürfen, können Sie die langsamen Funktionen in in C geschriebene Erweiterungen extrahieren, die vorkompiliert werden und keinen Interpreter-Overhead verursachen. Sie können SRP immer noch überall verwenden, aber Ihr Code wird zwischen Python und C aufgeteilt. Ob dies für die Wartbarkeit besser oder schlechter ist, als wenn Sie SRP selektiv aufgeben, sich aber nur an Python-Code halten, hängt von Ihrem Team ab, aber ob Sie leistungskritische Bereiche haben Code ist zweifellos schneller als selbst der optimierteste reine Python-Code, der von CPython interpretiert wird. Viele der schnellsten mathematischen Bibliotheken von Python verwenden diese Methode (numpy und scipy IIRC). Welches ist ein schöner Einstieg in Fall 2 ...

Fall 2.) Wenn Sie Python-Code haben, der C-Erweiterungen verwendet (oder sich auf Bibliotheken stützt, die C-Erweiterungen verwenden), ist PyPy möglicherweise nützlich oder nicht, je nachdem, wie sie geschrieben wurden. Siehe http://doc.pypy.org/en/latest/extending.html Informationen finden Die Zusammenfassung lautet jedoch, dass CFFI nur einen minimalen Overhead hat, während CTypes langsamer ist (die Verwendung mit PyPy ist möglicherweise sogar langsamer als CPython).

Cython ( https://cython.org/ ) ist eine weitere Option, mit der ich nicht so viel Erfahrung habe. Ich erwähne es der Vollständigkeit halber, damit meine Antwort "für sich" stehen kann, beanspruche aber kein Fachwissen. Aufgrund meiner eingeschränkten Nutzung hatte ich das Gefühl, dass ich mich mehr anstrengen musste, um die gleichen Geschwindigkeitsverbesserungen zu erzielen, die ich mit PyPy "kostenlos" erreichen konnte, und wenn ich etwas Besseres als PyPy brauchte, war es genauso einfach, meine eigene C-Erweiterung zu schreiben ( Das hat den Vorteil, wenn ich den Code an einer anderen Stelle wiederverwende oder einen Teil davon in eine Bibliothek extrahiere. Mein gesamter Code kann weiterhin unter jedem Python-Interpreter ausgeführt werden und muss nicht von Cython ausgeführt werden.

Ich habe Angst davor, in Cython "eingesperrt" zu sein, während jeder für PyPy geschriebene Code auch unter CPython laufen kann.

** Einige zusätzliche Hinweise zu PyPy in der Produktion

Seien Sie sehr vorsichtig, wenn Sie Entscheidungen treffen, die den praktischen Effekt haben, dass Sie sich in PyPy in einer großen Codebasis "einschließen". Da einige (sehr beliebte und nützliche) Bibliotheken von Drittanbietern aus den oben genannten Gründen nicht gut funktionieren, kann es später zu sehr schwierigen Entscheidungen kommen, wenn Sie feststellen, dass Sie eine dieser Bibliotheken benötigen. Meine Erfahrung besteht hauptsächlich darin, mit PyPy einige (aber nicht alle) Mikrodienste zu beschleunigen, die in einer Unternehmensumgebung, in der die Komplexität unserer Produktionsumgebung vernachlässigbar ist, leistungsabhängig sind (wir haben bereits mehrere Sprachen implementiert, einige mit unterschiedlichen Hauptversionen wie 2.7 vs 3.5 läuft sowieso).

Ich habe festgestellt, dass sowohl PyPy als auch CPython mich regelmäßig gezwungen haben, Code zu schreiben, der sich nur auf Garantien stützt, die von der Sprachspezifikation selbst gegeben wurden, und nicht auf Implementierungsdetails, die jederzeit geändert werden können. Möglicherweise stellt das Nachdenken über solche Details eine zusätzliche Belastung dar, aber ich fand es in meiner beruflichen Entwicklung wertvoll und finde es "gesund" für das gesamte Python-Ökosystem.

Steven Jackson
quelle
Ja! Ich habe überlegt, mich in diesem Fall auf C-Erweiterungen zu konzentrieren, anstatt das Prinzip aufzugeben und Wild-Code zu schreiben. Die anderen Antworten erweckten den Eindruck, dass es langsam wäre, unabhängig davon, ob ich vom Referenzinterpreter gewechselt habe Eine vernünftige Herangehensweise aus Ihrer Sicht?
Lucasgcb
1
Erhalten Sie mit Fall 1 (2. Abs.) nicht den gleichen Overhead , der die Funktionen aufruft , auch wenn die Funktionen selbst erfüllt sind?
Ewan
CPython ist der einzige Dolmetscher, der allgemein ernst genommen wird. PyPy ist interessant , findet aber sicherlich keine breite Akzeptanz. Darüber hinaus ist es das Verhalten unterscheidet sich von CPython, und es funktioniert nicht mit einigen wichtigen Pakete, zB scipy. Nur wenige vernünftige Entwickler würden PyPy für die Produktion empfehlen. Insofern ist die Unterscheidung zwischen Sprache und Implementierung in der Praxis unerheblich.
jpmc26
Ich denke du hast den Nagel auf den Kopf getroffen. Es gibt keinen Grund, warum Sie keinen besseren Interpreter oder Compiler haben könnten. Python als Sprache ist ihm nicht eigen. Sie sind nur mit praktischen Realitäten stecken
Ewan
@ jpmc26 Ich habe PyPy in der Produktion verwendet und empfehle dies anderen erfahrenen Entwicklern. Es eignet sich hervorragend für Microservices, die falconframework.org für APIs für leichtgewichtige Pausen verwenden (als ein Beispiel). Unterschiedliches Verhalten, da Entwickler sich auf Implementierungsdetails verlassen, die KEINE Garantie für die Sprache darstellen, ist kein Grund, PyPy nicht zu verwenden. Es ist ein Grund, Ihren Code umzuschreiben. Derselbe Code kann ohnehin fehlerhaft sein, wenn CPython Änderungen an seiner Implementierung vornimmt (die Sie vornehmen können, solange sie der Sprachspezifikation entsprechen).
Steven Jackson