Czy Python optymalizuje zmienną, która jest używana tylko jako wartość zwracana?

106

Czy jest jakaś ostateczna różnica między następującymi dwoma fragmentami kodu? Pierwsza przypisuje wartość do zmiennej w funkcji, a następnie zwraca tę zmienną. Druga funkcja po prostu zwraca wartość bezpośrednio.

Czy Python zamienia je w równoważny kod bajtowy? Czy któryś z nich jest szybszy?

Przypadek 1 :

def func():
    a = 42
    return a

Przypadek 2 :

def func():
    return 42
Jayesh
źródło
5
Jeśli użyjesz dis.dis(..)obu, zobaczysz, że istnieje różnica , więc tak. Ale w większości rzeczywistych aplikacji narzut tego w porównaniu z opóźnieniem przetwarzania w funkcji nie jest tak duży.
Willem Van Onsem
4
Istnieją dwie możliwości: (a) Zamierzasz wywołać tę funkcję wiele (tj. Co najmniej milion) razy w ciasnej pętli. W takim przypadku nie powinieneś w ogóle wywoływać funkcji Pythona, ale zamiast tego powinieneś wektoryzować pętlę za pomocą czegoś w rodzaju biblioteki numpy. (b) Nie będziesz wywoływał tej funkcji wiele razy. W takim przypadku różnica w szybkości między tymi funkcjami jest zbyt mała, aby się o nią martwić.
Arthur Tacca

Odpowiedzi:

138

Nie, to nieprawda .

Kompilacja do kodu bajtowego CPythona jest przekazywana tylko przez mały optymalizator wizjera, który jest przeznaczony do wykonywania tylko podstawowych optymalizacji (patrz test_peepholer.py w zestawie testów, aby uzyskać więcej informacji na temat tych optymalizacji).

Aby zobaczyć, co naprawdę się wydarzy, użyj dis*, aby zobaczyć wygenerowane instrukcje. Dla pierwszej funkcji zawierającej przypisanie:

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

Natomiast dla drugiej funkcji:

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

Dwie kolejne (szybkie) instrukcje są używane w pierwszej: STORE_FASTi LOAD_FAST. Tworzą one szybkie zapisywanie i pobieranie wartości w fastlocalstablicy bieżącej ramki wykonania. Następnie w obu przypadkach RETURN_VALUEwykonywany jest a . Tak więc druga jest bardzo nieznaczna szybsza ze względu na mniej poleceń potrzebnych do wykonania.

Ogólnie należy pamiętać, że kompilator CPython jest konserwatywny w optymalizacjach, które wykonuje. Nie jest i nie stara się być tak sprytny, jak inne kompilatory (które generalnie mają również znacznie więcej informacji do pracy). Głównym celem projektu, oprócz oczywistej poprawności, jest a) zachowanie prostoty i b) jak najszybsza kompilacja ich, aby nawet nie zauważyć, że istnieje faza kompilacji.

W końcu nie powinieneś martwić się drobnymi problemami, takimi jak ten. Korzyść w szybkości jest niewielka, stała i przyćmiona przez narzut wynikający z faktu, że Python jest interpretowany.

* disto mały moduł Pythona, który dezasembluje Twój kod, możesz go użyć, aby zobaczyć kod bajtowy Pythona, który wykona maszyna wirtualna.

Uwaga: jak stwierdzono również w komentarzu @Jorn Vernee, jest to specyficzne dla implementacji Pythona w CPythonie. Inne implementacje mogą wykonywać bardziej agresywne optymalizacje, jeśli sobie tego życzą, CPython nie.

Dimitris Fasarakis Hilliard
źródło
11
Nie jestem osobą w Pythonie (c ++), więc nie wiem, jak to działa pod maską, ale czy pierwszy przypadek nie powinien zostać zoptymalizowany pod kątem drugiego? Porządny kompilator C ++ dokonałby takiej optymalizacji.
NathanOliver
7
@NathanOliver to naprawdę nie jest, Python zrobi to, co tu powiedziano, nawet nie próbując grać mądrze.
Dimitris Fasarakis Hilliard
80
Fakt, że całkowicie rozsądne i inteligentne przypuszczenie @ NathanOlivera na to pytanie jest całkowicie błędne, jest moim zdaniem dowodem, że nie jest to „oczywiste”, „bzdury”, „głupie” pytanie, na które można odpowiedzieć „poświęcić chwilę na zastanowienie”, jak chciałby, byśmy wierzyli w TigerhawkT3. To ważne, interesujące pytanie, na które nie byłem pewien odpowiedzi, mimo że od lat jestem profesjonalnym programistą Pythona.
Mark Amery,
Kompilator Pythona jest w najlepszym razie „konserwatywny”, a nie „bardzo konserwatywny”. Głównym celem projektu nie jest bycie „tak szybkim, jak to tylko możliwe ... więc nawet nie zauważysz, że istnieje faza kompilacji”. To kwestia drugorzędna, po „prostocie”. Funkcja z dużymi stałymi, takimi jak „1 << (2 ** 34)” i „b'x '* (2 ** 32)”, zajmuje kilka sekund na kompilację i generowanie stałych o rozmiarze GB, nawet jeśli funkcja nigdy nie jest biegać. Kompilator odrzuci nawet duży ciąg. Proponowane poprawki dla tych przypadków zostały odrzucone, ponieważ spowodowałyby, że kompilator byłby zbyt skomplikowany.
Andrew Dalke
@AndrewDalke dzięki za komentarz wtajemniczonych, poprawiłem sformułowanie, aby rozwiązać problemy, które wskazałeś.
Dimitris Fasarakis Hilliard
3

Oba są zasadniczo takie same, z wyjątkiem tego, że w pierwszym przypadku obiekt 42jest po prostu przypisany do zmiennej o nazwie alub, innymi słowy, nazwy (tj. a) Odnoszą się do wartości (tj 42.). Z technicznego punktu widzenia nie wykonuje żadnego zadania, w tym sensie, że nigdy nie kopiuje żadnych danych.

Podczas returning to nazwane powiązanie ajest zwracane w pierwszym przypadku, podczas gdy obiekt 42jest zwracany w drugim przypadku.

Aby uzyskać więcej informacji, zapoznaj się z tym wspaniałym artykułem Neda Batcheldera

kmario23
źródło