14

If you look at the following timings:

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

There is a significant difference in execution speed between

reduce(int.__mul__,range(10000)) and reduce(mul,range(10000)) with the latter being faster.

using the dis module to look at what was happening:

Using int.__mul__ method:

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
>>>

And the operator mul method

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
>>>

They appear the same, so why is there a difference in execution speed? I am referring to the CPython implementation of Python


The same happens on 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
4

1 回答 1

14

int.__mul__ is a slot wrapper, namely, a PyWrapperDescrObject, while operator.mul is a buit-in function. I think the opposite execution speed is caused by this difference.

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

When we call a PyWrapperDescrObject, wrapperdescr_call is called.


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;
}

Let us look at what we found!

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

A new PyWrapper object has been constructed. It would slow down the execution speed significantly. Sometimes, it takes more time to create a new object than to run a simple function.
Thus, it is not surprised that int.__mul__ is slower than operator.mul.

于 2013-05-19T13:12:20.660 回答