为啥在 Python 2.7 中手动字符串反转比切片反转更糟糕? Slice 中使用的算法是啥?
Posted
技术标签:
【中文标题】为啥在 Python 2.7 中手动字符串反转比切片反转更糟糕? Slice 中使用的算法是啥?【英文标题】:Why manual string reverse is worse than slice reverse in Python 2.7? What is the algorithm being used in Slice?为什么在 Python 2.7 中手动字符串反转比切片反转更糟糕? Slice 中使用的算法是什么? 【发布时间】:2014-10-05 04:23:28 【问题描述】:低于 Slice 和手动反向操作的性能差异。如果是这样,那是什么原因呢?
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
【问题讨论】:
查看操作码会很有趣,但请记住,第二个示例创建了第二个列表(作为范围调用的结果),而第一个没有(当然不是 python range) - 所以这可能是原因之一。 两个版本都涉及循环,但是对于切片,循环由 python 解释器(用 C 编写)执行,因此比 python 循环快得多。 【参考方案1】:用于反转列表的切片符号下降到 C 中,这比纯 Python 实现的反转要快得多。例如,在纯 python 方法中,python 解释器必须读取、解码和执行字节码中的每条指令,而 C 调用将在本地执行,不会受到这样的惩罚。这种惩罚还扩展到诸如索引项目时的方法查找等事情,而在 C 调用中没有方法,只是地址算术。 C 实现是如此高效,以至于它甚至不需要专门的反向切片函数,并且仍然优于纯 python 实现。相反,它会创建切片的副本并在原地反转切片(在其他地方完成)。
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;
【讨论】:
【参考方案2】:3 个不同版本的 Dissasembly -(没有屏幕截图):
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
【讨论】:
【参考方案3】:这是字节码
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)
在第二种方法中,您将找到长度,创建一个带范围的副本并创建变量 i。
也可以使用 reversed 创建可迭代对象。
【讨论】:
以上是关于为啥在 Python 2.7 中手动字符串反转比切片反转更糟糕? Slice 中使用的算法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 Python 2.7 中两级字典的值都指向同一个对象?
当我的包是 Python 3.6 时,为啥 conda 要将所有内容降级到 Python 2.7?