1. ホーム
  2. python

[解決済み] Python 3でx**4.0がx**4より速いのはなぜですか?

2022-04-24 20:43:41

質問

なぜ 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 をfloatにすると、24.1~4あたりを移動しているだけです。

浮動小数点数の変換と2の累乗が関係してるんだろうけど、よくわかんないんだよね。

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


タイガーホークT3 は、ループの外では起きないとのご指摘をいただきました。調べてみたところ、この状況は(私が見た限りでは)ループの外側で ベース が上がってきています。それについて何か心当たりはありますか?

解決方法を教えてください。

<ブロッククオート

なぜ x**4.0 より速く より x**4 Python 3 では * ?

パイソン3 int オブジェクトは、任意のサイズに対応するように設計された本格的なオブジェクトです。 Cレベルではそのように扱われます。 (として宣言されます(すべての変数が PyLongObject * タイプで long_pow ). また、これによって、その指数関数がより多くの トリッキー 面倒くさい を弄る必要があるため ob_digit という配列で、その値を表現しています。( ブレイブのソース -- 参照してください。 Pythonで大きな整数のためのメモリ割り当てを理解する の詳細については PyLongObject s.)

パイソン float オブジェクトは、逆に は変換することができます をC double 型を使用します。 PyFloat_AsDouble )、演算を行うことができます。 これらのネイティブな型を使って . これはすごい なぜなら、関連するエッジケースをチェックした後、Pythonが プラットフォームの pow ( Cの pow ということです。 ) を使って、実際の指数計算を処理します。

/* 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); 

ここで iviw は、私たちのオリジナルの PyFloatObject をCとして double s.

何はともあれ Python 2.7.13 私にとっての要因 2~3 が速くなり、逆の挙動を示しています。

前事実 も説明しています。 Python 2 と 3 の間の不一致は、興味深いので、このコメントも取り上げようと思いました。

Python 2では、旧来の int オブジェクトとは異なります。 int オブジェクトは、Python 3では(すべて int オブジェクトは、3.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'> との比較では <type 'int'> をラップした場合、その x の名前を long の呼び出しは、Python 2で(実質的に強制的に 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つのスニペットで int から long このキャストは、(@pydsinger が指摘したように) 遅くなる要因ではありません。の実装は long_pow です。(ステートメントを単独で long(x) を見てください)。

<ブロッククオート

[...] ループの外では起こりません。[...] 何か心当たりはありますか?

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

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.' 唯一の違いは LOAD_CONST をロードし、フロート 256.0 の代わりに、int 型の 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にのみ適用されます。他の実装では異なる動作をするかもしれません。