次の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 * x
を2 *(x * x)
で置き換えた場合、2.04
と2.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
まず、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桁の値であり、x
と2*x
はループのすべての反復で1桁の値ですが、x*x
はx >= 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)
がちょうどより多くの数字を処理する必要があります。)
整数の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つの方法があります。
Pythonにも同じ問題があります。 x
が2x < B < x²
などの数値の場合、x² = aB+b
とともにa,b <B
を使用します。 x²
は2つのスロットに格納されていますが、(a|b)
に注意してください。計算により、(ここでキャリーを管理することなく):
(x*x)*2 => (a|b)*2 => (2*a|2*b)
(2*x)*x => (2x)*x =>(2a|2b)
最初のケースでは、2*
操作が2回行われますが、最初のケースでは1回だけです。それが違いを説明しています。
ベンチマークが正しい(チェックしなかった)場合、Python整数は2つの異なるものである可能性があるという事実から来る可能性があります。サイズが大きくなります(計算が遅くなります)。最初の構文では、最初の操作の後、サイズが小さく保たれますが、2番目の構文では、大きな整数を含む2つの操作が行われる場合があります。
私が知ることができることから、それは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