【问题标题】:int.__mul__ , executes 2X slower than operator.mulint.__mul__ ,执行速度比 operator.mul 慢 2 倍
【发布时间】:2013-05-19 09:59:56
【问题描述】:

如果您查看以下时序:

C:\Users\Henry>python -m timeit -s "mul = int.__mul__" "reduce(mul,range(10000))"
1000 loops, best of 3: 908 usec per loop

C:\Users\Henry>python -m timeit -s "from operator import mul" "reduce(mul,range(10000))"
1000 loops, best of 3: 410 usec per loop

两者的执行速度存在显着差异

reduce(int.__mul__,range(10000))reduce(mul,range(10000)) 后者更快。

使用dis 模块查看发生了什么:

使用int.__mul__方法:

C:\Users\Henry>python
Python 2.7.4 (default, Apr  6 2013, 19:55:15) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> mul = int.__mul__
>>> def test():
...     mul(1,2)
...
>>> import dis
>>> dis.dis(test)
  2           0 LOAD_GLOBAL              0 (mul)
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (2)
              9 CALL_FUNCTION            2
             12 POP_TOP
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>>

还有运算符mul方法

C:\Users\Henry>python
Python 2.7.4 (default, Apr  6 2013, 19:55:15) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from operator import mul
>>> def test():
...     mul(1,2)
...
>>> import dis
>>> dis.dis(test)
  2           0 LOAD_GLOBAL              0 (mul)
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (2)
              9 CALL_FUNCTION            2
             12 POP_TOP
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>>

它们看起来一样,那么为什么执行速度会有所不同?我指的是Python的CPython实现


同样的情况发生在 python3 上:

$ python3 -m timeit -s 'mul=int.__mul__;from functools import reduce' 'reduce(mul, range(10000))'
1000 loops, best of 3: 1.18 msec per loop
$ python3 -m timeit -s 'from operator import mul;from functools import reduce' 'reduce(mul, range(10000))'
1000 loops, best of 3: 643 usec per loop
$ python3 -m timeit -s 'mul=lambda x,y:x*y;from functools import reduce' 'reduce(mul, range(10000))'
1000 loops, best of 3: 1.26 msec per loop

【问题讨论】:

  • 您正在查看test() 的字节码反汇编,它只调用mul,因此在两种情况下都是相同的。 mul 的两个实现可能不同。
  • @HristoIliev 谢谢,我没有说它只是拆卸测试。我想这更有意义。到时候我会进一步研究这些是如何实现的。
  • 你用的是python二吗?问题可能是 int 的 mul 会溢出并调用 long 的 mul 而操作员避免这种额外的调用。
  • 使用profile.run('reduce(mul, range(10000))') 会显示更奇怪的结果:int.__mul__operator.mul 都只执行了 5 个函数调用(这意味着某些东西已被优化)。使用mul=lambda x,y:x*y 执行大约10k 函数调用(不应优化),但所用时间与int.__mul__ 相同。
  • @Bakuriu 我根本不相信这与这个问题有关(您可能应该将其编辑掉)函数调用似乎是由 Python 本身而不是由 @987654339 中的函数调用的函数调用@调用C中的函数@

标签: python c


【解决方案1】:

int.__mul__是一个slot wrapper,即PyWrapperDescrObject,而operator.mul是一个内置函数。 我认为相反的执行速度是由这种差异造成的。

>>> int.__mul__
<slot wrapper '__mul__' of 'int' objects>
>>> operator.mul
<built-in function mul>

当我们调用PyWrapperDescrObject 时,会调用wrapperdescr_call


static PyObject *
wrapperdescr_call(PyWrapperDescrObject *descr, PyObject *args, PyObject *kwds)
{
    Py_ssize_t argc;
    PyObject *self, *func, *result;

    /* Make sure that the first argument is acceptable as 'self' */
    assert(PyTuple_Check(args));
    argc = PyTuple_GET_SIZE(args);
    if (argc d_type->tp_name);
        return NULL;
    }
    self = PyTuple_GET_ITEM(args, 0);
    if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
                                  (PyObject *)(descr->d_type))) {
        PyErr_Format(PyExc_TypeError,
                     "descriptor '%.200s' "
                     "requires a '%.100s' object "
                     "but received a '%.100s'",
                     descr_name((PyDescrObject *)descr),
                     descr->d_type->tp_name,
                     self->ob_type->tp_name);
        return NULL;
    }

    func = PyWrapper_New((PyObject *)descr, self);
    if (func == NULL)
        return NULL;
    args = PyTuple_GetSlice(args, 1, argc);
    if (args == NULL) {
        Py_DECREF(func);
        return NULL;
    }
    result = PyEval_CallObjectWithKeywords(func, args, kwds);
    Py_DECREF(args);
    Py_DECREF(func);
    return result;
}

让我们看看我们发现了什么!

func = PyWrapper_New((PyObject *)descr, self);

已经构造了一个新的 PyWrapper 对象。它会显着降低执行速度。 有时,创建一个新对象比运行一个简单的函数需要更多的时间。
因此,int.__mul__operator.mul 慢也就不足为奇了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-20
    • 2014-09-12
    • 2023-03-08
    • 2021-04-12
    • 1970-01-01
    • 2015-12-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多