1

関数 A または関数 B の 2 つのサブ関数のうちの 1 つを実行することを決定するブール論理テストを実行するメイン関数があるとします。メイン関数は 10 億回ループされますが、論理テストの値は定数です (プログラムの開始時にユーザーが入力します)。

これを書くには 2 つの方法が考えられます: 1) 論理テストを関数 A に埋めます。少なくとも理論的には、論理テストは 10 億回実行する必要があり、効率的とは言えません。2) メイン関数の前に論理テストを行います。メイン関数をメイン関数 1 とメイン関数 2 (実行するサブ関数を除いて同一) に分割し、論理テストを使用して実行するメイン関数を決定します。ここでは、論理テストは 1 回だけ実行されますが、この実装では冗長なコードが作成されます。

実装 1) と 2) の間で計算効率に違いはありますか? 言い換えれば、Python はこれら 2 つの実装を機械語レベルで同等にするために自動最適化を行いますか?

4

2 に答える 2

2

@mmgpは両方の点で正しいですが、CPythonはそのような最適化を行いません。これが、Pythonが得意とする種類のコードのボトルネックになる可能性はほとんどありません。3番目のオプションがあります。使用する関数をパラメーターとして渡すことができます。

>>> def g1():
...         print 'g1'
...     
>>> def g2():
...         print 'g2'
...     
>>> def subfunc(fn):
...         fn()
...     
>>> def caller(a):
...         f = g1 if a else g2
...         for i in range(2):
...                 subfunc(f)
...         
>>> caller(True)
g1
g1
>>> caller(False)
g2
g2

サブ関数はまったく同じままで、テストをループから引き上げました。

于 2013-02-13T00:01:37.050 に答える
1

timeitパタシュが示唆するように、推測しようとするのではなく、テストに使用しましょう。で魔法%timeitを使いますipython。もっと単純だからです。コードは次のとおりです。

In [275]: def ff(): pass

In [276]: def ft(): pass

In [277]: def f1(b): # naive implementation
   .....:     for i in range(1000000):
   .....:         if b: ft()
   .....:         else: ff()

In [278]: %timeit f1(True)
10 loops, best of 3: 117 ms per loop

In [279]: def f2(b): # DSM's implementation
   .....:     f = ft if b else ff
   .....:     for i in range(1000000):
   .....:         f()

In [280]: %timeit f2(True)
10 loops, best of 3: 99.2 ms per loop

したがって、少なくとも私の Mac の CPython 3.3.0 64 ビットでは、少し高速です。

ただし、Python の最適化について少しでも知っていれば、グローバル変数をローカル変数に移動した場合とほぼ同じパフォーマンスの向上であることに気付くでしょう。したがって、ブール式を巻き上げずに同じことを行うことで、方程式からそれを取り除きましょう。

In [277]: def f3(b): # Just local binding, no if hoisting
   .....:     f, g = ft, ff
   .....:     for i in range(1000000):
   .....:         if b: f()
   .....:         else: g()
In [286]: %timeit f3(True)
10 loops, best of 3: 94.8 ms per loop

OPの意図した最適化と、変更なしで3.xおよび2.xで動作するコードを含む、より完全なテストをまとめ、 Apple 2.7.2、python.org 3.3.0、PyPy 1.9.0/に対して実行しました2.7.2、および Jython 2.5.2 (Mac ですべて 64 ビット ビルド、次に Cython 0.17.1 pyximport (Python 3.3.0 の下) を使用して Cython コードと同じソースをコンパイルします。

                 3.3.0   2.7.2   PyPy    Jython  Cython
orig             1.136   1.519   0.091   1.680   0.448
OP optimization  1.119   1.362   0.034   1.613   0.460
rebinding        0.936   1.369   0.030   1.492   0.137
DSM version      0.936   1.329   0.031   1.523   0.138

したがって、ループの外側で名前をバインドすると、速度が 1.1 倍から 3 倍になるようです。さらに、比較をループの外に引き上げると、さらに 3% ほどの結果が得られる可能性がありますが、これはすべて、CPython の代わりに PyPy を使用すること、Python の代わりに Cython を使用すること、または 2.x の代わりに 3.x を使用することと比較して何もありません。実際の Cython またはカスタム C コードを作成するか、ループを に移動するnumpyと、さらに高速になります。

そして考えてみれば、これは理にかなっている。10 億回の bool 比較またはグローバル ルックアップのコストが重要である場合、10 億回の関数呼び出しと 10 億回のインタープリター ループのコストは、はるかに重要になります。それを最適化することに煩わされていない場合(そして、mapインタープリターを切り替えたり、コードを書き換えたりしても、ループの代わりにジェネレーター式、リスト内包表記、呼び出しなどを使用するだけでそれを行うことができますnumpy。実現不可能です)、小さなことを気にする必要はありません。

そして明らかに、最後の 3% が実際に違いを生む場合は、実際に関心のあるプラットフォームで、より現実的なテストを実行する必要があります。

おそらく DSM の実装を使用する価値はありますが、より慣用的で読みやすいためであり、高速である場合とそうでない場合があるからではありません。

于 2013-02-13T00:11:20.090 に答える