当前位置: 首页 > 知识库问答 >
问题:

Python3.x整数比位移快2倍?

曾珂
2023-03-14

我在查看sorted_containers的源代码时,惊讶地看到了这一行:

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

这里的load是一个整数。为什么在一个地方使用位移位,而在另一个地方使用乘法?位移位可能比整数除以2快,这似乎是合理的,但为什么不把乘法也换成移位呢?我对以下案例进行了基准测试:

  1. (乘以,除法)
  2. (shift,shift)
  3. (次数,移位)
  4. (移位、除法)
# 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)])

以上是问题的原话。丹·盖茨在他的回答中提供了一个极好的解释。

为了完整起见,下面是不应用乘法优化时较大x的示例说明。

共有1个答案

商畅
2023-03-14

这似乎是因为在CPython3.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 dihtml" target="_blank">gits, 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
    }

相反,左移位不是这样优化的,每次左移位都将被移位的整数作为数组处理。特别是,如果您查看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);

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

您没有问到整数地板分割比右移更差的性能,因为这符合您(和我)的期望。但一个小正数除以另一个小正数也不如小乘法优化。每个//都使用函数long_divrem()计算商和余数。这个余数是为一个带乘法的小除数计算的,并存储在一个新分配的整数对象中,在这种情况下,该对象会被立即丢弃。

 类似资料:
  • 以下Python3.x整数乘法的平均运算时间在1.66s到1.77s之间: 如果将替换为,则需要在和之间。怎么会呢? 另一方面,在Java中则相反:在Java中更快。Java测试链接:为什么在Java中2*(i*i)比2*i*i快? 我运行每个版本的程序10次,以下是结果。

  • 本文向大家介绍Python3.x中自定义比较函数,包括了Python3.x中自定义比较函数的使用技巧和注意事项,需要的朋友参考一下 在Python3.x的世界里,cmp函数没有了。那么sorted,min,max等需要比较函数作为参数的函数该如何用呢? 以min函数的定义为例,有两种重载形式: 单参数(一个迭代器): 多参数(多个待比较内容): 本文主要讨论key=func参数的使用 。举例说明吧

  • 基于此,我认为我应该完全开始在中使用

  • 我正在比较填充整数列表所需的时间与整数向量。 每个矢量 令我惊讶的是,填充列表比填充整数向量快100倍。我希望填充整数的向量要快得多,因为向量在内存中是连续的,插入要快得多。 填充列表怎么会比填充向量快100倍而不是10倍呢?我肯定我缺少一些导致这种情况的概念或想法。 这是我用来生成结果的代码 有人能给我解释一下为什么会这样吗???

  • 问题内容: 考虑以下代码(其中byteIndex是一个int): 这会产生错误 编译时(必需字节,位于int)。 代码 编译良好。 这是什么问题,我该如何解决第一个示例以允许将整数值按int值进行移位? 编辑:根据评论,这是一个更完整的示例: 和给出的错误是: 问题答案: 将您的行转换为此:- 您的RHS是一个整数,您需要将其转换为字节。 上面的代码将正常工作..不管有没有的到 因此,您也可以:-

  • < b >想改进这个问题?通过编辑此帖子添加详细信息并澄清问题。 我知道java没有无符号整数,但是在Java SE 8中,有使用整数数据类型来执行无符号算术的方法。在java中,如何将位转换成“无符号整数”?