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

Uri*_*Uri 34 python

我指的是这个问题,特别是对@David Robinson和@mgilson的第一个答案的评论: 在列表中求和每个元组的第二个值

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

structure = [('a', 1), ('b', 3), ('c', 2)]
Run Code Online (Sandbox Code Playgroud)

第一个答案:

sum(n for _, n in structure)
Run Code Online (Sandbox Code Playgroud)

第二个答案:

sum(x[1] for x in structure)
Run Code Online (Sandbox Code Playgroud)

根据讨论,第一个答案是快50%.

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

Mar*_*ers 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        
Run Code Online (Sandbox Code Playgroud)

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

UNPACK_SEQUENCE操作码的源代码特殊情况下,蟒元组或列表解包,其中所述序列长度的长度参数精确匹配:

        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
Run Code Online (Sandbox Code Playgroud)

上面的代码进入元组的本机结构并直接检索值; 不需要使用大量调用,例如PyObject_GetItem必须考虑对象可以是自定义python类.

BINARY_SUBSCR只为Python优化列表 ; 任何不是本机python列表的东西都需要PyObject_GetItem调用.

  • 尽管如此,我希望`BINARY_SUBSCRIPT`是一个非常有效的操作(在查找之后),而`UNPACK_SEQUENCE`不是(O(1)对O(n),因为它是)并且下标操作需要少一个存储操作.事实上,你的代码格式是误导性的 - 两个操作都采用相同数量的操作码,你还没有真正解释为什么解包比下标更快:特别是,为什么解包不需要通过` PyObject_GetItem`? (2认同)
  • @KonradRudolph:扩展。有趣的是,如果你用列表替换元组,情况很可能会发生逆转。 (2认同)

Amb*_*ber 17

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

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