次の2つのコードスニペット間に最終的な違いはありますか?1つ目は、関数内の変数に値を割り当て、その変数を返します。2番目の関数は、値を直接返すだけです。
Pythonはそれらを同等のバイトコードに変換しますか?それらの1つはより高速ですか?
ケース1:
def func():
a = 42
return a
ケース2:
def func():
return 42
次の2つのコードスニペット間に最終的な違いはありますか?1つ目は、関数内の変数に値を割り当て、その変数を返します。2番目の関数は、値を直接返すだけです。
Pythonはそれらを同等のバイトコードに変換しますか?それらの1つはより高速ですか?
ケース1:
def func():
a = 42
return a
ケース2:
def func():
return 42
回答:
いいえ、ありません。
CPythonバイトコードへのコンパイルは、基本的な最適化のみを行うように設計された小さなのぞき穴オプティマイザーのみを通過します(これらの最適化の詳細については、テストスイートのtest_peepholer.pyを参照してください)。
実際に何が発生するかdis
を確認するには、*を使用して生成された命令を確認します。割り当てを含む最初の関数の場合:
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
一方、2番目の関数の場合:
dis(func2)
2 0 LOAD_CONST 1 (42)
2 RETURN_VALUE
最初の2つの(高速)命令が使用されます:STORE_FAST
とLOAD_FAST
。これらfastlocals
は、現在の実行フレームの配列に値をすばやく格納して取得します。次に、どちらの場合でも、a RETURN_VALUE
が実行されます。したがって、2番目のコマンドは、実行に必要なコマンドが少ないため、わずかに速くなります。
一般に、CPythonコンパイラーは、実行する最適化において保守的であることに注意してください。これは、他のコンパイラ(一般に、処理する情報がはるかに多いコンパイラ)ほど賢くはありません。明らかに正しいことは別として、主な設計目標は、a)シンプルに保つこと、およびb)これらをコンパイルする際にできるだけ迅速にして、コンパイルフェーズが存在することに気付かないようにすることです。
結局、このような小さな問題に悩まされるべきではありません。速度の利点は小さく、一定であり、Pythonが解釈されるという事実によってもたらされるオーバーヘッドによって小さくなります。
* dis
は、コードを逆アセンブルする小さなPythonモジュールです。これを使用して、VMが実行するPythonバイトコードを確認できます。
注: @Jorn Verneeのコメントでも述べられているように、これはPythonのCPython実装に固有のものです。他の実装では、必要に応じてより積極的な最適化を行う場合がありますが、CPythonは行いません。
最初のケースでは、オブジェクト42
が単に名前付き変数に割り当てられていること、a
つまり、名前(ie a
)が値(ie 42
)を参照していることを除いて、どちらも基本的に同じです。データをコピーしないという意味で、技術的には割り当てを行いません。
処理中、最初のケースではreturn
この名前付きバインディングa
が返さ42
れ、2番目のケースではオブジェクトが返されます。
詳細については、Ned Batchelderによるこの素晴らしい記事を参照してください。
dis.dis(..)
両方で使用すると、違いがあることがわかります。そうです。しかし、ほとんどの実際のアプリケーションでは、関数の処理の遅延と比較した場合のオーバーヘッドはそれほど大きくありません。