2

条件文(python 2.6.6)を評価するときに、ブール値を否定することでペナルティがあるかどうかを調べようとしていました。私は最初にこの簡単なテストを試しました(elseブランチなし)

>>> import timeit
>>> timeit.timeit("if not True: pass", number=100000)
0.011913061141967773
>>> timeit.timeit("if True: pass", number=100000)
0.018882036209106445

だから私は、passステートメントが少なくともいくつかの操作であるnoopに変換される可能性があるため、結果が歪んでいると思います。

私は2回目の試行を行い、次の結果を得ました。

>>> timeit.timeit("a=False\nif not a: pass\nelse: pass", number=100000)
0.02387714385986328
>>> timeit.timeit("a=False\nif a: pass\nelse: pass", number=100000)
0.015386819839477539
>>> timeit.timeit("a=True\nif a: pass\nelse: pass", number=100000)
0.02389812469482422
>>> timeit.timeit("a=True\nif not a: pass\nelse: pass", number=100000)
0.015424966812133789

大きなペナルティが発生するとは思っていませんでしたが、この結果から、ブランチの評価はelse暗黙のブランチよりも安価であるように見えthenます。そして、その違いは非常に大きいです!

したがって、3回目の試行では、次の結果が返されます。

>>> timeit.timeit("if True: a=1\nelse: a=1", number=100000)
0.022008895874023438
>>> timeit.timeit("if not True: a=1\nelse: a=1", number=100000)
0.022121906280517578

そしてついに私は期待された結果を得ました。好奇心から、私は最後に試しました:

>>> timeit.timeit("if False: a=1\nelse: a=1", number=100000)
0.02385997772216797
>>> timeit.timeit("if not False: a=1\nelse: a=1", number=100000)
0.02244400978088379

thenそしてそれだけです...ブランチにつながる否定された条件がなぜ速いのか私にはわかりません。

何が起こっているのでしょうか?

これらの結果はすべて私のコンピューターで再現可能です。何度実行しても、ほぼ同じ結果が得られます。

else: passコンパイラがその部分を完全に削除した可能性があるため、最初のテストは歪んでいたと思います。それは可能ですか?

このすべての結果は、CPUの分岐予測に関連している可能性がありますか?

他に考えられる原因はありますか?

4

1 に答える 1

17

まず、「現実世界」のニュースです。「if not」または「if ..pass else...」を記述するとアプリケーションのパフォーマンスに影響を与えるような状況に本当にいる場合は、本格的なプロファイリングを行うことをお勧めします。 、ネイティブコードで内部ループを書き換えます-CythonまたはCを使用します(さらに、Fortranなどの他のオプション-Pythonは統合に優れています)。

そうでなければ、これらは無関係な実装の詳細だと思います。"dis" を使用してコード シーケンスを逆アセンブルすると、何が起こっているのかがわかります - しかし、このレベルでの最適化は、Python では実用的な価値がありません - 単一の関数呼び出しは、演算子を使用して暗黙的に行われる場合でも、約 1 桁余分に必要になります。ステートメントの実行に比べて時間がかかりifます。

ここ:

>>> dis.dis(compile("if not True: pass", "None", "exec",))
  1           0 LOAD_NAME                0 (True)
              3 POP_JUMP_IF_TRUE         9
              6 JUMP_FORWARD             0 (to 9)
        >>    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        
>>> dis.dis(compile("if True: pass", "None", "exec",))
  1           0 LOAD_NAME                0 (True)
              3 POP_JUMP_IF_FALSE        9
              6 JUMP_FORWARD             0 (to 9)
        >>    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        
>>> 

ご覧のとおり、"not" を使用する場合と使用しない場合で生成されるバイトコードに違いはありませんが、ジャンプ演算子の操作については、どちらの場合も同じ時間がかかるはずです。

于 2012-10-30T14:05:26.907 に答える