Python 3.x整数の場合、ビットシフトよりも2倍高速ですか?


150

私はsorted_containersのソースを見ていて、この行を見驚いた:

self._load, self._twice, self._half = load, load * 2, load >> 1

これloadは整数です。ある場所でビットシフトを使用し、別の場所で乗算を使用するのはなぜですか?2による整数除算よりもビットシフトの方が速い場合があるのは理にかなっていますが、乗算もシフトで置き換えてみませんか?私は以下のケースをベンチマークしました:

  1. (回、除算)
  2. (シフト、シフト)
  3. (回、シフト)
  4. (シフト、除算)

そして#3は他の選択肢よりも一貫して速いことがわかりました:

# self._load, self._twice, self._half = load, load * 2, load >> 1

import random
import timeit
import pandas as pd

x = random.randint(10 ** 3, 10 ** 6)

def test_naive():
    a, b, c = x, 2 * x, x // 2

def test_shift():
    a, b, c = x, x << 1, x >> 1    

def test_mixed():
    a, b, c = x, x * 2, x >> 1    

def test_mixed_swapped():
    a, b, c = x, x << 1, x // 2

def observe(k):
    print(k)
    return {
        'naive': timeit.timeit(test_naive),
        'shift': timeit.timeit(test_shift),
        'mixed': timeit.timeit(test_mixed),
        'mixed_swapped': timeit.timeit(test_mixed_swapped),
    }

def get_observations():
    return pd.DataFrame([observe(k) for k in range(100)])

ここに画像の説明を入力してください ここに画像の説明を入力してください

質問:

テストは有効ですか?もしそうなら、なぜ(乗算、シフト)が(シフト、シフト)より速いのですか?

Ubuntu 14.04でPython 3.5を実行しています。

編集する

上記は、質問の元のステートメントです。ダンゲッツは彼の答えで優れた説明を提供します。

完全を期すために、ここではx、乗算の最適化が適用されない場合の大規模なサンプル図を示します。

ここに画像の説明を入力してください ここに画像の説明を入力してください


3
どこで定義しましたxか?
JBernardo 2016

3
リトルエンディアン/ビッグエンディアンを使用して違いがあるかどうかを本当に確認したいと思います。本当にクールな質問ところで!
LiGhTx117 2016

1
@ LiGhTx117 x非常に大きくない限り、それは操作とは無関係であると期待します。
Dan Getz

1
2で割るのではなく、0.5を掛けるとどうなりますか?mipsアセンブリプログラミングの以前の経験から、除算は通常、とにかく乗算演算になります。(これは除算ではなくビットシフトの選択を説明します)
Sayse

2
それを浮動小数点に変換する@Sayse。うまくいけば、整数の床除算は、浮動小数点を経由する往復よりも速くなります。
Dan Getz 2016

回答:


155

これは、小さな数値の乗算がCPython 3.5で最適化されているため、小さな数値による左シフトが最適化されていないためと思われます。正の左シフトは常に、計算の一部として結果を格納するためのより大きな整数オブジェクトを作成しますが、テストで使用したソートの乗算では、特別な最適化によりこれが回避され、正しいサイズの整数オブジェクトが作成されます。これは、Pythonの整数実装のソースコードで確認できます。

Pythonの整数は任意精度であるため、整数の「桁」の配列として格納され、整数の桁あたりのビット数に制限があります。したがって、一般的なケースでは、整数を含む操作は単一の操作ではなく、複数の「数字」の場合を処理する必要があります。pyport.h、このビット限界はとして定義されているそれ以外の場合は64ビットプラットフォーム上の30ビットまたは15ビット。(ここでは、説明を簡単にするために、この30と呼ぶことにします。ただし、32ビット用にコンパイルされたPythonを使用している場合、ベンチマークの結果はx、32,768未満かどうかに依存することに注意してください。)

操作の入力と出力がこの30ビットの制限内に留まる場合、操作は一般的な方法ではなく最適化された方法で処理できます。整数乗算の実装の始まりは次のとおりです。

static PyObject *
long_mul(PyLongObject *a, PyLongObject *b)
{
    PyLongObject *z;

    CHECK_BINOP(a, b);

    /* fast path for single-digit multiplication */
    if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
        stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);
#ifdef HAVE_LONG_LONG
        return PyLong_FromLongLong((PY_LONG_LONG)v);
#else
        /* if we don't have long long then we're almost certainly
           using 15-bit digits, so v will fit in a long.  In the
           unlikely event that we're using 30-bit digits on a platform
           without long long, a large v will just cause us to fall
           through to the general multiplication code below. */
        if (v >= LONG_MIN && v <= LONG_MAX)
            return PyLong_FromLong((long)v);
#endif
    }

したがって、それぞれが30ビットの桁に収まる2つの整数を乗算する場合、これは配列として整数を処理するのではなく、CPythonインタープリターによる直接乗算として行われます。(MEDIUM_VALUE()正の整数オブジェクトで呼び出されると、最初の30ビットの数字が取得されます。)結果が単一の30ビットの数字に収まる場合、PyLong_FromLongLong()これは比較的少数の演算でこれに気付き、格納する1桁の整数オブジェクトを作成します。それ。

対照的に、左シフトはこのように最適化されておらず、すべての左シフトは配列としてシフトされる整数を扱います。特に、のソースコードを見るlong_lshift()と、小さいが正の左シフトの場合、2桁の整数オブジェクトが常に作成されます(長さが後で1に切り捨てられる場合のみ((/*** ***/

static PyObject *
long_lshift(PyObject *v, PyObject *w)
{
    /*** ... ***/

    wordshift = shiftby / PyLong_SHIFT;   /*** zero for small w ***/
    remshift  = shiftby - wordshift * PyLong_SHIFT;   /*** w for small w ***/

    oldsize = Py_ABS(Py_SIZE(a));   /*** 1 for small v > 0 ***/
    newsize = oldsize + wordshift;
    if (remshift)
        ++newsize;   /*** here newsize becomes at least 2 for w > 0, v > 0 ***/
    z = _PyLong_New(newsize);

    /*** ... ***/
}

整数除算

右シフトと比較して整数フロア除算のパフォーマンスが悪いことについては質問しなかった。しかし、小さい正の数を別の小さい正の数で除算することも、小さい乗算ほど最適化されていません。Every は、関数を使用して//剰余の両方を計算しますlong_divrem()。この残りと小さな除数のために計算され、乗算、および新しく割り当てられたIntegerオブジェクトに格納されているこのような状況で直ちに廃棄されます。


1
指摘してくれてありがとう。言うまでもなく、これは全体として優れた答えです。
hilberts_drinking_problem 2016年

優れた質問に対するよく研究され、書かれた回答。x最適化された範囲外のタイミングのグラフを表示すると面白いかもしれません。
Barmar 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.