pythonで累乗するよりも、乗算する方がはるかに速い理由について知りたいです(ただし、私が読んだことから、これは他の多くの言語でも当てはまる可能性があります)。実行する方がはるかに高速です
x*x
より
x**2
**演算子はより一般的であり、分数の累乗も処理できると思います。しかし、それが非常に遅い理由である場合、int指数のチェックを実行してから、乗算を実行しないのはなぜですか?
編集:これが私が試したサンプルコードです...
def pow1(r, n):
for i in range(r):
p = i**n
def pow2(r, n):
for i in range(r):
p = 1
for j in range(n):
p *= i
さて、pow2は単なる簡単な例であり、明らかに最適化されていません。
しかし、それでも、n = 2およびr = 1,000,000を使用すると、pow1は約2500ミリ秒かかり、pow2は約1700ミリ秒かかります。
nの値が大きい場合、pow1はpow2よりもはるかに高速になることを認めます。しかし、それはそれほど驚くべきことではありません。
基本的に、ナイーブな乗算はO(n)で、定数係数が非常に低くなります。べき乗は、定数係数が高いO(log n)です(テストが必要な特殊なケースがあります)。 。分数の指数、負の指数など)。編集:明確にするために、それはO(n)ここで、nは指数です。
もちろん、素朴なアプローチはnが小さいほど高速になります。実際には指数数学の小さなサブセットしか実装していないため、定数係数は無視できます。
小切手の追加も費用がかかります。あなたはいつもそこにそのチェックをしたいですか?コンパイルされた言語は、実行時のコストがなく、コンパイル時のコストだけであるため、定数指数をチェックして、それが比較的小さい整数であるかどうかを確認できます。インタープリター言語はそのチェックを行わない可能性があります。
その種の詳細が言語によって指定されていない限り、それは特定の実装次第です。
Pythonは、フィードする指数の分布を認識していません。 99%が整数以外の値になる場合、コードで毎回整数をチェックして、実行時間をさらに遅くしますか?
指数チェックでこれを行うと、2の単純な累乗ではない場合が非常に遅くなるため、必ずしも勝利とは限りません。ただし、指数が事前にわかっている場合(たとえば、リテラル2が使用されている場合)、生成されたバイトコードは単純なのぞき穴最適化で最適化できます。おそらく、これは単に行う価値があるとは考えられていません(かなり特殊なケースです)。
これは、そのような最適化を行う概念の簡単な証明です(デコレータとして使用可能)。注:実行するには、 byteplay モジュールが必要です。
import byteplay, timeit
def optimise(func):
c = byteplay.Code.from_code(func.func_code)
prev=None
for i, (op, arg) in enumerate(c.code):
if op == byteplay.BINARY_POWER:
if c.code[i-1] == (byteplay.LOAD_CONST, 2):
c.code[i-1] = (byteplay.DUP_TOP, None)
c.code[i] = (byteplay.BINARY_MULTIPLY, None)
func.func_code = c.to_code()
return func
def square(x):
return x**2
print "Unoptimised :", timeit.Timer('square(10)','from __main__ import square').timeit(10000000)
square = optimise(square)
print "Optimised :", timeit.Timer('square(10)','from __main__ import square').timeit(10000000)
タイミングは次のとおりです。
Unoptimised : 6.42024898529
Optimised : 4.52667593956
[編集]実際、もう少し考えてみると、この最適化が行われないのには非常に理由があります。誰かが__mul__
メソッドと__pow__
メソッドをオーバーライドし、それぞれに対して異なることを行うユーザー定義クラスを作成しないという保証はありません。安全に行う唯一の方法は、スタックの最上位にあるオブジェクトが同じ結果「x**2
」と「x*x
」になることを保証できる場合ですが、それを解決するのは大変です。もっと強く。例えば。私の例では、任意のオブジェクトをsquare関数に渡すことができるため、これは不可能です。
べき乗剰余を使用したb ^ pの実装
def power(b, p):
"""
Calculates b^p
Complexity O(log p)
b -> double
p -> integer
res -> double
"""
res = 1
while p:
if p & 0x1: res *= b
b *= b
p >>= 1
return res
これがそれほど重要であるとは誰も予想していなかったと思います。通常、本格的な計算を実行する場合は、Fortran、C、C++などで実行します(おそらくPythonから呼び出します)。
すべてをexp(n * log(x))として扱うことは、nが整数でないか、かなり大きい場合にうまく機能しますが、小さい整数の場合は比較的非効率的です。 nが十分に小さい整数であるかどうかを確認するには時間がかかり、複雑さが増します。
チェックの価値があるかどうかは、予想される指数、ここで最高のパフォーマンスを得ることがどれほど重要か、および余分な複雑さのコストによって異なります。どうやら、Guidoと残りのPythonギャングは、チェックを行う価値がないと判断しました。
必要に応じて、独自の繰り返し乗算関数を作成できます。
x x x x xはどうですか?それでもx ** 5より速いですか?
int指数が大きくなると、累乗の取得は乗算よりも高速になる可能性があります。しかし、実際のクロスオーバーが発生する数はさまざまな条件によって異なるため、私の意見では、言語/ライブラリレベルで最適化が実行されなかった(または実行できなかった)理由です。しかし、ユーザーはまだいくつかの特別な場合のために最適化することができます:)