2013-05-19 6 views
14

Se si guardano le seguenti tempistiche:int .__ mul__, esegue 2X più lento di operator.mul

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 

V'è una differenza significativa nella velocità di esecuzione tra

reduce(int.__mul__,range(10000)) e reduce(mul,range(10000)) con l'essere più veloce quest'ultimo .

utilizzando il modulo dis di guardare a ciò che stava accadendo:

Utilizzando int.__mul__ metodo:

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

E il mul metodo di operatore

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

Essi appaiono lo stesso, quindi perché è c'è una differenza nella velocità di esecuzione? Mi riferisco all'attuazione CPython di pitone


Lo stesso accade in 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 
+5

Stai guardando lo smontaggio del bytecode di 'test()' e chiama semplicemente 'mul', quindi è lo stesso in entrambi i casi. Sono le due implementazioni di 'mul' che probabilmente differiscono. –

+0

@HristoIliev Grazie, non ho detto che era solo il test di smontaggio. Suppongo che abbia molto più senso. Vedrò come questi sono stati implementati un po 'di più allora. – HennyH

+0

Stai usando python due? Il problema potrebbe essere che il mul di int tracolerà e chiamerà a lungo mul mentre l'operatore eviterà queste chiamate extra. – Bakuriu

risposta

14

int.__mul__ è un involucro fessura, cioè un PyWrapperDescrObject, mentre operator.mul è una funzione buit-in. Penso che l'opposta velocità di esecuzione sia causata da questa differenza.

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

Quando chiamiamo un PyWrapperDescrObject, wrapperdescr_call si chiama.


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

Diamo un'occhiata a quello che abbiamo trovato!

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

Un nuovo oggetto PyWrapper è stato costruito. Ridurrebbe significativamente la velocità di esecuzione. A volte, ci vuole più tempo per creare un nuovo oggetto piuttosto che eseguire una semplice funzione.
Pertanto, non è sorpreso che int.__mul__ sia più lento di operator.mul.