如何解压缩比通过索引访问更快?

时间:2012-10-23 06:08:10

标签: python

我指的是这个问题,特别是对@David Robinson和@mgilson的第一个答案的评论: Sum the second value of each tuple in a list

最初的问题是将每个tuble的第二个值相加:

structure = [('a', 1), ('b', 3), ('c', 2)]

第一回答:

sum(n for _, n in structure)

第二回答:

sum(x[1] for x in structure)

根据讨论,第一个答案要快50%。

一旦我弄明白第一个答案是什么(来自Perl,我用Google搜索了python中的特殊_变量意味着),我想知道怎样才会出现一个纯子集任务(只获得每个元组的第二个元素)与获取和绑定到变量两个元素)实际上更慢?是否缺少优化Python中的索引访问的机会?我错过了第二个答案需要时间吗?

2 个答案:

答案 0 :(得分:35)

如果你看一下python字节码,很快就会明白为什么解包更快:

>>> import dis
>>> def unpack_or_index(t=(0, 1)):
...     _, x = t
...     x = t[1]
... 
>>> dis.dis(unpack_or_index)
  2           0 LOAD_FAST                0 (t)
              3 UNPACK_SEQUENCE          2
              6 STORE_FAST               1 (_)
              9 STORE_FAST               2 (x)

  3          12 LOAD_FAST                0 (t)
             15 LOAD_CONST               1 (1)
             18 BINARY_SUBSCR       
             19 STORE_FAST               2 (x)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE        

元组解包操作是一个简单的字节码(UNPACK_SEQUENCE),而索引操作必须调用元组(BINARY_SUBSCR)上的方法。解包操作可以在python评估循环中内联进行,而订阅调用需要使用PyObject_GetItem查找元组对象上的函数以检索值。

UNPACK_SEQUENCE opcode source code特殊情况下python元组或列表解包,其中序列长度与参数长度完全匹配:

        if (PyTuple_CheckExact(v) &&
            PyTuple_GET_SIZE(v) == oparg) {
            PyObject **items = \
                ((PyTupleObject *)v)->ob_item;
            while (oparg--) {
                w = items[oparg];
                Py_INCREF(w);
                PUSH(w);
            }
            Py_DECREF(v);
            continue;
        } // followed by an "else if" statement for a list with similar code

上面的代码进入元组的本机结构并直接检索值;不需要使用诸如PyObject_GetItem之类的大量调用,这些调用必须考虑到该对象可能是一个自定义的python类。

BINARY_SUBSCR opcode仅针对python 列表进行了优化;任何不是本机python列表的内容都需要PyObject_GetItem调用。

答案 1 :(得分:17)

索引通过__getitem__特殊方法,因此必须为每个项执行函数查找和执行。这意味着,对于n项列表,您最终会进行n次查找/调用。

使用本机列表/元组时,解包不需要处理;它只是通过__iter__这是一次调用,然后在C中解压缩结果序列。