web-dev-qa-db-ja.com

整数のPython 3.xでは、2 * x * xが2 *(x * x)よりも速いのはなぜですか?

次のPython 3.x整数乗算は、平均で1.66秒と1.77秒の間にかかります。

import time
start_time = time.time()
num = 0
for x in range(0, 10000000):
    # num += 2 * (x * x)
    num += 2 * x * x
print("--- %s seconds ---" % (time.time() - start_time))

2 * x * x2 *(x * x)で置き換えた場合、2.042.25の間を取ります。どうして?

一方、Javaでは2 * (x * x)がJavaの方が高速です。 Javaテストリンク: Javaで2 *(i * i)が2 * i * iより速いのはなぜですか?

プログラムの各バージョンを10回実行しました。結果は次のとおりです。

   2 * x * x        |   2 * (x * x)
---------------------------------------
1.7717654705047607  | 2.0789272785186768
1.735931396484375   | 2.1166207790374756
1.7093875408172607  | 2.024367570877075
1.7004504203796387  | 2.047525405883789
1.6676218509674072  | 2.254328966140747
1.699510097503662   | 2.0949244499206543
1.6889283657073975  | 2.0841963291168213
1.7243537902832031  | 2.1290600299835205
1.712965488433838   | 2.1942825317382812
1.7622807025909424  | 2.1200053691864014
38
Waqas Gondal

まず、Python 2.xには同じものが表示されないことに注意してください。

>>> timeit("for i in range(1000): 2*i*i")
51.00784397125244
>>> timeit("for i in range(1000): 2*(i*i)")
50.48330092430115

そのため、これはPython 3:で整数がどのように変更されたかによると考えられます。具体的には、Python 3はどこでもlong(任意の大きな整数)を使用します。

十分に小さい整数(ここで検討しているものを含む)の場合、CPythonは実際にはO(MN) 等級乗算アルゴリズムによる学年桁 (より大きい整数の場合は カラツバアルゴリズム )に切り替えます。これは source で確認できます。

x*xの桁数は、2*xまたはxの約2倍です(log(x2)= 2 log(x))。このコンテキストでの「数字」は10進数ではなく、30ビット値(CPythonの実装では1桁として扱われる)であることに注意してください。したがって、2は1桁の値であり、x2*xはループのすべての反復で1桁の値ですが、x*xx >= 2**15で2桁です。したがって、x >= 2**15の場合、2*x*xは1桁ごとの乗算のみを必要としますが、2*(x*x)は1桁と1桁ごとの乗算を必要とします(x*xには2つの30ビット桁があるため)。

これを直接見る方法は次のとおりです(Python 3):

>>> timeit("a*b", "a,b = 2, 123456**2", number=100000000)
5.796971936999967
>>> timeit("a*b", "a,b = 2*123456, 123456", number=100000000)
4.3559221399999615

繰り返しますが、これをPython 2と比較してください。

>>> timeit("a*b", "a,b = 2, 123456**2", number=100000000)
3.0912468433380127
>>> timeit("a*b", "a,b = 2*123456, 123456", number=100000000)
3.1120400428771973

(1つの興味深いメモ:ソースを見ると、アルゴリズムには実際に数値を二乗するための特別なケースがあることがわかります(ここで行っています)が、それでも2*(x*x)がちょうどより多くの数字を処理する必要があります。)

28
arshajii

整数のPythonインターン表現は特別で、30ビットのスロットを使用します:

In [6]: sys.getsizeof(2**30-1)
Out[6]: 28 # one slot + heading

In [7]: sys.getsizeof(2**30)
Out[7]: 32 # two slots 

したがって、すべてがPythonがベースB = 2**30 = 1 073 741 824 ~1 billionでカウントされるように発生します。

2 * 4 * 4を計算する人間には、2つの方法があります。

  • (2 * 4)* 4 = 8 * 4 = 32 = 30 + 2は、テーブルの追加がわかっている場合はすぐに発生します。
  • 2 *(4 * 4)= 2 * 16 = 2 * 10 + 2 * 6 =(2 * 10 + 10)+ 2 = 30 + 2演算を停止する必要があるため。

Pythonにも同じ問題があります。 x2x < B < x²などの数値の場合、x² = aB+bとともにa,b <Bを使用します。 は2つのスロットに格納されていますが、(a|b)に注意してください。計算により、(ここでキャリーを管理することなく):

   (x*x)*2 =>  (a|b)*2 => (2*a|2*b)
   (2*x)*x =>  (2x)*x =>(2a|2b)

最初のケースでは、2*操作が2回行われますが、最初のケースでは1回だけです。それが違いを説明しています。

7
B. M.

ベンチマークが正しい(チェックしなかった)場合、Python整数は2つの異なるものである可能性があるという事実から来る可能性があります。サイズが大きくなります(計算が遅くなります)。最初の構文では、最初の操作の後、サイズが小さく保たれますが、2番目の構文では、大きな整数を含む2つの操作が行われる場合があります。

4
Thomas Baruchel

私が知ることができることから、それは2 * (x * x)を使用するバージョンでもう少しメモリにアクセスすることになります。逆アセンブルされたバイトコードを印刷しましたが、次のことを証明しているようです:

2 * x * xの関連部分:

7          28 LOAD_FAST                1 (num)
           30 LOAD_CONST               3 (2)
           32 LOAD_FAST                2 (x)
           34 BINARY_MULTIPLY
           36 LOAD_FAST                2 (x)
           38 BINARY_MULTIPLY
           40 INPLACE_ADD
           42 STORE_FAST               1 (num)
           44 JUMP_ABSOLUTE           24

2 * (x * x)の関連部分:

  7          28 LOAD_FAST                1 (num)
             30 LOAD_CONST               3 (2)
             32 LOAD_FAST                2 (x)
             34 LOAD_FAST                2 (x)
             36 BINARY_MULTIPLY                 <=== 1st multiply x*x in a temp value
             38 BINARY_MULTIPLY                 <=== then multiply result with 2
             40 INPLACE_ADD
             42 STORE_FAST               1 (num)
             44 JUMP_ABSOLUTE           24
2
Eric Fortin