2011-11-09 20 views
79

私のコードを最適化しながら、私は次のことを実現:Python:なぜ、*と**が/とsqrt()より速いのですか?

>>> from timeit import Timer as T 
>>> T(lambda : 1234567890/4.0).repeat() 
[0.22256922721862793, 0.20560789108276367, 0.20530295372009277] 
>>> from __future__ import division 
>>> T(lambda : 1234567890/4).repeat() 
[0.14969301223754883, 0.14155197143554688, 0.14141488075256348] 
>>> T(lambda : 1234567890 * 0.25).repeat() 
[0.13619112968444824, 0.1281130313873291, 0.12830305099487305] 

とも:

>>> from math import sqrt 
>>> T(lambda : sqrt(1234567890)).repeat() 
[0.2597470283508301, 0.2498021125793457, 0.24994492530822754] 
>>> T(lambda : 1234567890 ** 0.5).repeat() 
[0.15409398078918457, 0.14059877395629883, 0.14049601554870605] 

私はそれのpythonはCで実装されている方法に関係していると仮定しますが、誰もが気になる場合、私は疑問に思いますなぜそうであるか説明する?

+0

あなたの質問(私はあなたの本当の質問に答えていると思われます)で受け入れた答えはあなたの質問のタイトルとはあまり関係ありません。あなたはそれを編集して、一定の折り畳みと関係があるでしょうか? –

+1

@ザンリンクス - こんにちは。明確にしてもよろしいですか?質問タイトルは、私が知りたいと思っていたことを正確に表現しています(なぜXはYよりも速いのですか)。そして、私が選んだ答えは、まさにそうです...私には完全にマッチしています...しかし、 – mac

+8

乗算関数と電力関数は、その性質上、除算関数とsqrt()関数よりも常に高速です。除算とルート演算は、一般的に一連の細かく細かい近似を使用する必要があり、乗算のように正しい答えに直接行くことはできません。 –

答えて

112

あなたの結果が(やや予期しない)理由は、Pythonが浮動小数点の乗算と累乗を含む定数式を折り畳むように見えるが、除算ではないように見えるということです。 math.sqrt()はバイトコードが存在せず、関数呼び出しを伴うので、別の獣です。 Pythonの2.6.5、次のコードで

x1 = 1234567890.0/4.0 
x2 = 1234567890.0 * 0.25 
x3 = 1234567890.0 ** 0.5 
x4 = math.sqrt(1234567890.0) 

は、次のバイトコードにコンパイルされます。

# x1 = 1234567890.0/4.0 
    4   0 LOAD_CONST    1 (1234567890.0) 
       3 LOAD_CONST    2 (4.0) 
       6 BINARY_DIVIDE  
       7 STORE_FAST    0 (x1) 

    # x2 = 1234567890.0 * 0.25 
    5   10 LOAD_CONST    5 (308641972.5) 
      13 STORE_FAST    1 (x2) 

    # x3 = 1234567890.0 ** 0.5 
    6   16 LOAD_CONST    6 (35136.418286444619) 
      19 STORE_FAST    2 (x3) 

    # x4 = math.sqrt(1234567890.0) 
    7   22 LOAD_GLOBAL    0 (math) 
      25 LOAD_ATTR    1 (sqrt) 
      28 LOAD_CONST    1 (1234567890.0) 
      31 CALL_FUNCTION   1 
      34 STORE_FAST    3 (x4) 

あなたが見ることができるように、彼らは「以来、乗算及び累乗はまったく時間がかかりませんコードがコンパイルされたときに再実行されます。ディビジョンは実行時に発生するので時間がかかります。平方根は、4つの演算の中で最も計算量の多い演算であるだけでなく、他の演算子ではない様々なオーバーヘッド(属性検索、関数呼び出しなど)も発生します。

あなたが一定の折りたたみの影響を排除した場合、別の乗算、除算にはほとんど存在しています:

In [16]: x = 1234567890.0 

In [17]: %timeit x/4.0 
10000000 loops, best of 3: 87.8 ns per loop 

In [18]: %timeit x * 0.25 
10000000 loops, best of 3: 91.6 ns per loop 

math.sqrt(x)は、したがってことができ、それは後者の特別なケースだと思われるので、少し速くx ** 0.5よりも実際にあると

In [19]: %timeit x ** 0.5 
1000000 loops, best of 3: 211 ns per loop 

In [20]: %timeit math.sqrt(x) 
10000000 loops, best of 3: 181 ns per loop 

編集2011-11-16:定数式の折りたたみは、PythonのPEによって行われるオーバーヘッドにもかかわらず、より効率的に行うことepholeオプティマイザソースコード(peephole.c)一定の分割が折り畳まれていない理由を説明し、次のコメントが含まれます。

case BINARY_DIVIDE: 
     /* Cannot fold this operation statically since 
      the result can depend on the run-time presence 
      of the -Qnew flag */ 
     return 0; 

-QnewフラグがPEP 238で定義された「真の除算を」有効にします。

+2

おそらく、ゼロ除算を「保護」していますか? – hugomg

+2

@missingno:どちらの引数もコンパイル時に知られているので、なぜそのような "保護"が必要なのかはわかりません。結果は+ inf、-inf、NaNのいずれかです。 – NPE

+1

おそらく 'from __future__ import division'テストでも同様のメソッドを使用します。 – Simon

関連する問題