Python 3でx ** 4.0がx ** 4より速いのはなぜですか?


164

なぜx**4.0より速いのですx**4か?CPython 3.5.2を使用しています。

$ python -m timeit "for x in range(100):" " x**4.0"
  10000 loops, best of 3: 24.2 usec per loop

$ python -m timeit "for x in range(100):" " x**4"
  10000 loops, best of 3: 30.6 usec per loop

上げた力を変更してその動作を確認しようとしました。たとえば、xを10または16の累乗にすると、30から35にジャンプしますが、フロートとして10.0だけ上げると、動いています。約24.1〜4。

float変換と2のべき乗と関係があるのではないかと思いますが、実際にはわかりません。

どちらの場合も2の累乗の方が速いことに気づきました。これらの計算は、インタプリタ/コンピュータにとってよりネイティブ/簡単だからです。しかし、それでも、フロートではほとんど動きません。2.0 => 24.1~4 & 128.0 => 24.1~4 だが 2 => 29 & 128 => 62


TigerhawkT3は、ループの外では発生しないことを指摘しました。私がチェックしたところ、状況は(私が見たものから)ベースが上昇しているときにのみ発生します。それについて何か考えはありますか?


11
それは価値がある:私のためのPython 2.7.13は2〜3倍速く、逆の振る舞い示します:整数の指数は浮動小数点の指数より速いです。

4
@Evert yup、私は14 usec x**4.0と3.9 を得ましたx**4
dabadaba 2017

回答:


161

なぜx**4.0 速くよりx**4Pythonの3の*

Python 3 intオブジェクトは、任意のサイズをサポートするように設計された本格的なオブジェクトです。そのため、それらはCレベルでそのように処理されます(すべての変数がPyLongObject *型として宣言される方法を参照してくださいlong_pow)。また、これを実行するには、値を表すために使用する配列をいじる必要があるため、指数の計算がより複雑面倒になりますob_digit。(勇敢な人々のためのソース。-参照:PyLongObjectsの詳細については、Pythonでの長整数のメモリ割り当てを理解する。)

float逆に、Python オブジェクトは(を使用して)Cの型に変換でき、操作はこれらのネイティブの型を使用して実行できますこれは素晴らしいです、関連するエッジケースをチェックした後、それをするのPythonを許可する、なぜならプラットフォームを使用するCのつまり、実際の累乗を処理するために):doublePyFloat_AsDoublepowpow

/* Now iv and iw are finite, iw is nonzero, and iv is
 * positive and not equal to 1.0.  We finally allow
 * the platform pow to step in and do the rest.
 */
errno = 0;
PyFPE_START_PROTECT("pow", return NULL)
ix = pow(iv, iw); 

ここでiviwPyFloatObjectCとしての元doubleのです。

それだけの価値2.7.13があるのですが、私にとってPython は1倍2~3高速で、逆の振る舞いを示します。

前の事実はPython 2とPython 3の間の不一致も説明しているので、興味深いので、このコメントにも対応したいと思いました。

Python 2では、Python 3のintオブジェクトとは異なる古いオブジェクトを使用していintます(int3.xのオブジェクトはすべてPyLongObjectタイプです)。Python 2では、オブジェクトの値に依存する違いがあります(または、サフィックスを使用する場合L/l)。

# Python 2
type(30)  # <type 'int'>
type(30L) # <type 'long'>

<type 'int'>あなたがここで見るには、同じ事ないfloatのが何をそれが安全にCに変換される、long 累乗は、その上で実行されるint_powそれはそうすることができる場合にはので、またレジスタに」日を置くようにコンパイラにヒント可能性が違いを生みます) :

static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
    register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */    

これにより、速度が向上します。

<type 'long'>sと<type 'int'>sの比較の低さを確認するために、Python 2の呼び出しでx名前をラップした場合long(本質的にlong_powは、Python 3のように強制的に使用するように)、速度の向上は消えます。

# <type 'int'>
(python2)  python -m timeit "for x in range(1000):" " x**2"       
10000 loops, best of 3: 116 usec per loop
# <type 'long'> 
(python2)  python -m timeit "for x in range(1000):" " long(x)**2"
100 loops, best of 3: 2.12 msec per loop

1つのスニペットはintto longを変換しますが、(@ pydsingerによって指摘されたように)は変換しませんが、このキャストはスローダウンの背後にある力ではありません。の実装long_powです。(ステートメントlong(x)を見るだけで時間を計る)。

[...]ループの外では発生しません。[...]それについて何か考えはありますか?

これは、CPythonのピープホールオプティマイザーで、定数を折りたたみます。指数の結果を見つけるための実際の計算はなく、値の読み込みのみなので、どちらの場合でも正確なタイミングは同じです。

dis.dis(compile('4 ** 4', '', 'exec'))
  1           0 LOAD_CONST               2 (256)
              3 POP_TOP
              4 LOAD_CONST               1 (None)
              7 RETURN_VALUE

同一のバイトコードが生成されますが'4 ** 4.'、唯一の違いは、がintではなくLOAD_CONSTfloat 256.0をロードすることです256

dis.dis(compile('4 ** 4.', '', 'exec'))
  1           0 LOAD_CONST               3 (256.0)
              2 POP_TOP
              4 LOAD_CONST               2 (None)
              6 RETURN_VALUE

したがって、時間は同じです。


*上記はすべて、Pythonのリファレンス実装であるCPythonにのみ適用されます。他の実装は異なる動作をする場合があります。


それが何であれ、それはaのループに関連しています。なぜなら、rangeタイミング**自体は整数と浮動小数点数の違いをもたらさないためです。
TigerhawkT3、2017

違いは変数を検索するときにのみ表示され(4**4と同じくらい高速です4**4.0)、この答えはそれについてまったく触れません。
TigerhawkT3 2017

1
しかし、定数は@ TigerhawkT3(dis(compile('4 ** 4', '', 'exec')))で折りたたまれるので、時刻はまったく同じになるはずです。
Dimitris Fasarakis Hilliard 2017

あなたの最後のタイミングはあなたが言うことを示さないようです。4-5の係数long(x)**2.よりもさらに高速ですlong(x)**2。(ただし、賛成投票の1つではありません)
Graipher

3
@ mbomb007 <type 'long'>Python 3 での型の削除は、おそらく言語を単純化するために行われた努力によって説明されています。1つの型で整数を表すことができる場合、2つよりも扱いやすくなります(必要に応じて1つの型から別の型に変換したり、ユーザーが混乱したりするなど)。スピードゲインはそれに次ぐものです。PEP 237の理論的根拠のセクションでは、さらにいくつかの洞察を提供しています。
Dimitris Fasarakis Hilliard 2017

25

バイトコードを見ると、式がまったく同じであることがわかります。唯一の違いは、の引数となる定数のタイプですBINARY_POWER。ですから、それは間違いなくint、浮動小数点数に変換されるためです。

>>> def func(n):
...    return n**4
... 
>>> def func1(n):
...    return n**4.0
... 
>>> from dis import dis
>>> dis(func)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4)
              6 BINARY_POWER
              7 RETURN_VALUE
>>> dis(func1)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4.0)
              6 BINARY_POWER
              7 RETURN_VALUE

更新:CPythonソースコードのObjects / abstract.cを見てみましょう。

PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{
    return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
}

PyNumber_Power通話ternary_opここに貼り付けることが長すぎるので、ここにリンクがあります

nb_powerスロットを呼び出し、引数としてx渡しyます。

最後に、Objects / floatobject.cのfloat_pow() 686行目で、実際の操作の直前に引数がCに変換されていることがわかります。double

static PyObject *
float_pow(PyObject *v, PyObject *w, PyObject *z)
{
    double iv, iw, ix;
    int negate_result = 0;

    if ((PyObject *)z != Py_None) {
        PyErr_SetString(PyExc_TypeError, "pow() 3rd argument not "
            "allowed unless all arguments are integers");
        return NULL;
    }

    CONVERT_TO_DOUBLE(v, iv);
    CONVERT_TO_DOUBLE(w, iw);
    ...

1
@Jean-FrançoisFabreこれは折り畳みが一定しているためだと思います。
Dimitris Fasarakis Hilliard 2017

2
変換があり、それらが別の方法で処理されないことの意味は、「間違いなく」、ソースなしでは少し伸びていると思います。
ミラドゥロ2017

1
@Mitch-特に、この特定のコードでは、これら2つの操作の実行時間に違いはありません。違いはOPのループでのみ発生します。この答えは結論に飛びついています。
TigerhawkT3、2017

2
なぜそれがfloat_pow遅い場合でも実行されないときだけ見ているのですか?
user2357112は2017

2
TigerhawkT3 @:4**44**4.0定折り畳まを取得します。それは完全に別の効果です。
user2357112は2017

-1

1つは正しいので、もう1つは近似です。

>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625

なぜその反対投票が反対投票したのかはわかりませんが、この回答では質問に回答できないためです。何かが正しいからといって、それが決して速いか遅いかを意味するわけではありません。1つはCの型で作業でき、もう1つはPythonオブジェクトで作業する必要があるため、もう1つより低速です。
Dimitris Fasarakis Hilliard、2018

1
説明ありがとう。まあ、私は本当にそれらをすべて正確に計算するよりも、12桁程度の数値の近似値だけを計算する方が速いことは明らかだと思いました。結局のところ、近似を使用する唯一の理由は、それらが計算を高速化することですよね?
Veky
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.