Optimiert Python eine Variable, die nur als Rückgabewert verwendet wird?

106

Gibt es einen endgültigen Unterschied zwischen den folgenden beiden Codefragmenten? Der erste weist einer Variablen in einer Funktion einen Wert zu und gibt diese Variable dann zurück. Die zweite Funktion gibt nur den Wert direkt zurück.

Verwandelt Python sie in äquivalenten Bytecode? Ist einer von ihnen schneller?

Fall 1 :

def func():
    a = 42
    return a

Fall 2 :

def func():
    return 42
Jayesh
quelle
5
Wenn Sie dis.dis(..)auf beiden verwenden, sehen Sie, dass es einen Unterschied gibt , also ja. In den meisten realen Anwendungen ist der Aufwand im Vergleich zur Verzögerung der Verarbeitung in der Funktion jedoch nicht so groß.
Willem Van Onsem
4
Es gibt zwei Möglichkeiten: (a) Sie werden diese Funktion viele (dh mindestens eine Million) Mal in einer engen Schleife aufrufen. In diesem Fall sollten Sie überhaupt keine Python-Funktion aufrufen, sondern Ihre Schleife mithilfe der Numpy-Bibliothek vektorisieren. (b) Sie werden diese Funktion nicht so oft aufrufen. In diesem Fall ist der Geschwindigkeitsunterschied zwischen diesen Funktionen zu gering, um sich Sorgen zu machen.
Arthur Tacca

Antworten:

138

Nein, das tut es nicht .

Die Kompilierung in CPython-Bytecode wird nur über einen kleinen Gucklochoptimierer geleitet , der nur für grundlegende Optimierungen ausgelegt ist (siehe test_peepholer.py) zu diesen Optimierungen finden in der Testsuite unter ).

Verwenden Sie dis*, um die generierten Anweisungen anzuzeigen, um zu sehen, was tatsächlich passieren wird. Für die erste Funktion, die die Zuordnung enthält:

from dis import dis
dis(func)
  2           0 LOAD_CONST               1 (42)
              2 STORE_FAST               0 (a)

  3           4 LOAD_FAST                0 (a)
              6 RETURN_VALUE

Während für die zweite Funktion:

dis(func2)
  2           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

Im ersten werden zwei weitere (schnelle) Anweisungen verwendet: STORE_FASTund LOAD_FAST. Diese ermöglichen ein schnelles Speichern und Abrufen des Werts im fastlocalsArray des aktuellen Ausführungsrahmens. Dann wird in beiden Fällen a RETURN_VALUEdurchgeführt. Der zweite ist also ganz leicht schneller, da weniger Befehle zur Ausführung benötigt werden.

Beachten Sie im Allgemeinen, dass der CPython-Compiler bei den durchgeführten Optimierungen konservativ ist . Es ist nicht und versucht nicht, so intelligent zu sein wie andere Compiler (die im Allgemeinen auch viel mehr Informationen zum Arbeiten haben). Abgesehen von der offensichtlichen Richtigkeit besteht das Hauptziel des Designs darin, a) es einfach zu halten und b) diese so schnell wie möglich zu kompilieren, damit Sie nicht einmal bemerken, dass eine Kompilierungsphase existiert.

Am Ende sollten Sie sich nicht mit kleinen Problemen wie diesem beschäftigen. Der Geschwindigkeitsvorteil ist winzig, konstant und wird durch den Overhead, der durch die Interpretation von Python entsteht, in den Schatten gestellt.

* disist ein kleines Python-Modul, das Ihren Code zerlegt. Sie können es verwenden, um den Python-Bytecode anzuzeigen, den die VM ausführen wird.

Hinweis: Wie auch in einem Kommentar von @Jorn Vernee angegeben, ist dies spezifisch für die CPython-Implementierung von Python. Andere Implementierungen führen möglicherweise aggressivere Optimierungen durch, wenn sie dies wünschen, CPython jedoch nicht.

Dimitris Fasarakis Hilliard
quelle
11
Keine Python-Person (c ++), daher weiß ich nicht, wie es unter der Haube funktioniert, aber sollte der erste Fall nicht auf den zweiten Fall optimiert werden? Ein anständiger C ++ - Compiler würde diese Optimierung vornehmen.
NathanOliver
7
@ NathanOliver tut es wirklich nicht, Python wird das tun, was hier gesagt wurde, ohne zu versuchen, es klug zu spielen.
Dimitris Fasarakis Hilliard
80
Die Tatsache, dass @ NathanOlivers völlig vernünftige und intelligente Vermutung einer Antwort auf diese Frage völlig falsch ist, ist für mich ein Beweis dafür, dass dies keine "selbsterklärende", "unsinnige", "dumme" Frage ist, die beantwortet werden kann indem wir uns "einen Moment Zeit zum Nachdenken nehmen", wie TigerhawkT3 uns glauben machen würde. Es ist eine gültige, interessante Frage, deren Antwort ich mir nicht sicher war, obwohl ich jahrelang ein professioneller Python-Programmierer war.
Mark Amery
Pythons Compiler ist bestenfalls "konservativ", nicht "sehr konservativ". Das Hauptziel des Designs ist es nicht, "so schnell wie möglich zu sein ... damit Sie nicht einmal bemerken, dass eine Kompilierungsphase existiert". Das ist zweitrangig, nach "halte es einfach". Eine Funktion mit großen Konstanten wie "1 << (2 ** 34)" und "b'x '* (2 ** 32)" benötigt einige Sekunden, um Konstanten in GB-Größe zu kompilieren und zu generieren, selbst wenn die Funktion niemals ist Lauf. Die große Zeichenfolge wird sogar vom Compiler verworfen. Vorgeschlagene Korrekturen für diese Fälle wurden abgelehnt, da sie den Compiler zu komplex machen würden.
Andrew Dalke
@ AndrewDalke, danke für den Insider-Kommentar dazu. Ich habe den Wortlaut angepasst, um die Probleme zu beheben, auf die Sie hingewiesen haben.
Dimitris Fasarakis Hilliard
3

Beide sind grundsätzlich gleich, außer dass das Objekt im ersten Fall 42einfach einer Variablen mit dem Namen zugewiesen wird aoder mit anderen Worten, Namen (dh a) sich auf Werte (dh 42) beziehen . Es führt technisch keine Zuordnung in dem Sinne durch, dass es niemals Daten kopiert.

Währenddessen returnwird diese benannte Bindung aim ersten Fall zurückgegeben, während das Objekt 42im zweiten Fall zurückgegeben wird.

Weitere Informationen finden Sie in diesem großartigen Artikel von Ned Batchelder

kmario23
quelle