Why manual string reverse is worse than slice reverse in Python 2.7? What is the algorithm being used in Slice?
Asked Answered
S

3

5

Below the performance difference between Slice and manual reverse operation. If this is the case, What is the reason for that?

timeit.timeit("a[::-1]","a=[1,2,3,4,5,6]",number=100)
6.054327968740836e-05

timeit.timeit("[a[i] for i in range(len(a)-1,-1,-1)]","a=[1,2,3,4,5,6]",number=100)
0.0003132152330920235
Snowfield answered 12/8, 2014 at 7:29 Comment(2)
It would be interesting to see the opcodes, but remember that the 2nd example creates a 2nd list (as a result of the range call), where as the first one doesn't (well not a python range) - so that might be one of the reasons.Supplication
Both versions involve loops, but with the slice the loop is performed by the python interpreter (written in C) and thus much faster than the python loop.Old
L
8

Here's the bytecode

from dis import dis
a = [1,2,3,4,5,6]

def func1():
    a[::-1]

def func2():
    [a[i] for i in range(len(a)-1,-1,-1)]

def func3():
    reversed(a)

In the second method, you're finding the length, creating a copy with range and creating the variable i.

bytecode

Can also use reversed to create an iterable.

bytecode2

Lassie answered 12/8, 2014 at 8:5 Comment(0)
A
2

The slice notation for reversing a list drops down into C, which is considerably faster than a pure python implementation of a reverse. For instance, in the pure python approach the python interpreter must read, decode, and execute each instruction in the byte code, whereas the C call will be executing natively and suffer no such penalty. This penalty also extends to things such as method lookups when indexing an item and so forth, whereas in the C call there is no method, just address arithmetic. So efficient is the C implementation that it doesn't even bother with a specialised reversed slice function, and still beats the pure python implementation. Rather it creates a copy of the slice and the reverses the slice in place (done else where).

List slice code for cpython:

static PyObject *
list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh)
{
    PyListObject *np;
    PyObject **src, **dest;
    Py_ssize_t i, len;
    if (ilow < 0)
        ilow = 0;
    else if (ilow > Py_SIZE(a))
        ilow = Py_SIZE(a);
    if (ihigh < ilow)
        ihigh = ilow;
    else if (ihigh > Py_SIZE(a))
        ihigh = Py_SIZE(a);
    len = ihigh - ilow;
    np = (PyListObject *) PyList_New(len);
    if (np == NULL)
        return NULL;

    src = a->ob_item + ilow;
    dest = np->ob_item;
    for (i = 0; i < len; i++) {
        PyObject *v = src[i];
        Py_INCREF(v);
        dest[i] = v;
    }
    return (PyObject *)np;
}
Alodi answered 12/8, 2014 at 8:48 Comment(0)
S
0

The Dissasembly for 3 different versions - (without screen shots) :

import dis

a = [1,2,3,4,5,6]

def x( l ):
    return l[::-1]

dis.dis(x)
2           0 LOAD_FAST                0 (l)
            3 LOAD_CONST               0 (None)
            6 LOAD_CONST               0 (None)
            9 LOAD_CONST               1 (-1)
           12 BUILD_SLICE              3
           15 BINARY_SUBSCR       
          16 RETURN_VALUE        

def y( l ):
   return [l[i] for i in range(len(l)-1,-1,-1)]

dis.dis(y)
2           0 BUILD_LIST               0
            3 LOAD_GLOBAL              0 (range)
            6 LOAD_GLOBAL              1 (len)
            9 LOAD_FAST                0 (l)
           12 CALL_FUNCTION            1
           15 LOAD_CONST               1 (1)
           18 BINARY_SUBTRACT     
           19 LOAD_CONST               2 (-1)
           22 LOAD_CONST               2 (-1)
           25 CALL_FUNCTION            3
           28 GET_ITER            
      >>   29 FOR_ITER                16 (to 48)
           32 STORE_FAST               1 (i)
           35 LOAD_FAST                0 (l)
           38 LOAD_FAST                1 (i)
           41 BINARY_SUBSCR       
           42 LIST_APPEND              2
           45 JUMP_ABSOLUTE           29
      >>   48 RETURN_VALUE        

def z( l ):
    return [i for i in reversed(a)]

dis.dis(z)
2           0 BUILD_LIST               0
            3 LOAD_GLOBAL              0 (reversed)
            6 LOAD_GLOBAL              1 (a)
            9 CALL_FUNCTION            1
           12 GET_ITER            
      >>   13 FOR_ITER                12 (to 28)
           16 STORE_FAST               1 (i)
           19 LOAD_FAST                1 (i)
           22 LIST_APPEND              2
           25 JUMP_ABSOLUTE           13
      >>   28 RETURN_VALUE    
Supplication answered 12/8, 2014 at 8:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.