Подтвердить что ты не робот

Почему распаковка происходит быстрее, чем доступ к индексу?

Я имею в виду этот вопрос, и особенно комментарии к первому ответу от @David Robinson и @mgilson: Суммируйте второе значение каждого кортежа в списке

Первоначальный вопрос состоял в том, чтобы суммировать второе значение каждой канавки:

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

Первый ответ:

sum(n for _, n in structure)

Второй ответ:

sum(x[1] for x in structure)

Согласно обсуждению, первый ответ на 50% быстрее.

Как только я понял, что делает первый ответ (исходящий из Perl, я Googled для специальных переменных переменной в python), мне стало интересно, как получилось то, что появляется как задача чистого подмножества (получая только второй элемент каждого кортежа по сравнению с получением и привязкой к переменным оба элемента) на самом деле медленнее? Это недостающая возможность оптимизировать доступ к индексу в Python? Я пропустил что-то, что требуется для второго ответа?

4b9b3361

Ответ 1

Если вы посмотрите на байт-код 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 код кода операции специальные фрагменты кортежей 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 оптимизирован только для списков python; все, что не является родным списком python, требует вызова PyObject_GetItem.

Ответ 2

Индексация проходит через специальный метод __getitem__, который, таким образом, должен выполнять поиск и выполнение функций для каждого элемента. Это означает, что для списка элементов n вы завершаете поиск n запросов/вызовов.

Распаковка не должна иметь дело с этим при работе с собственными списками/кортежами; он просто проходит через __iter__, который является единственным вызовом, а затем распаковывает результирующую последовательность в C.