Почему mesh-код python медленнее, чем разложенный?

Я обнаружил удивительное поведение python при исследовании потока Почему чтение строк из stdin происходит намного медленнее на С++, чем на Python?.

Если я запускаю простой код python из этого потока

#!/usr/bin/env python
from __future__ import print_function
import time
import sys


count = 0
start_time = time.time()

for line in sys.stdin:
    count += 1

delta_sec = time.time() - start_time
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0:n} lines in {1:.2f} seconds. LPS: {2:n}".format(count, delta_sec, lines_per_sec))

он работает со скоростью 11.5M LPS, и когда я разлагаю весь script на одну функцию

#!/usr/bin/env python
from __future__ import print_function
import time
import sys


def test(input):
    count = 0
    start_time = time.time()

    for line in input:
        count += 1

    delta_sec = time.time() - start_time
    if delta_sec >= 0:
        lines_per_sec = int(round(count/delta_sec))
        print("Read {0:n} lines in {1:.2f} seconds. LPS: {2:n}".format(count, delta_sec, lines_per_sec))


if __name__ == "__main__":
    test(sys.stdin)

ускоряет скорость до 23M LPS.

Почему этот простой рефакторинг делает мой код в 2 раза быстрее?

Я запускал свои тесты с python2.7 на Ubuntu 13.10.

Ответы

Ответ 1

Наблюдение за байт-кодом помогло мне ответить на этот вопрос. Байт-код для рабочей части первого script:

 10          58 SETUP_LOOP              27 (to 88)
             61 LOAD_NAME                3 (sys)
             64 LOAD_ATTR                6 (stdin)
             67 GET_ITER         
        >>   68 FOR_ITER                16 (to 87)
             71 STORE_NAME               7 (line)
 11          74 LOAD_NAME                4 (count)
             77 LOAD_CONST               4 (1)
             80 INPLACE_ADD      
             81 STORE_NAME               4 (count)
             84 JUMP_ABSOLUTE           68
        >>   87 POP_BLOCK

И байт-код для соответствующей части второго script:

 12          18 SETUP_LOOP              24 (to 45)
             21 LOAD_FAST                0 (input)
             24 GET_ITER
        >>   25 FOR_ITER                16 (to 44)
             28 STORE_FAST               3 (line)
 13          31 LOAD_FAST                1 (count)
             34 LOAD_CONST               2 (1)
             37 INPLACE_ADD
             38 STORE_FAST               1 (count)
             41 JUMP_ABSOLUTE           25
        >>   44 POP_BLOCK

Я вижу, что фактическая разница между этими кодами заключается в использовании LOAD_NAME против LOAD_FAST и STORE_NAME против STORE_FAST. Документация http://docs.python.org/2.7/library/dis.html#opcode-LOAD_FAST гласит, что LOAD_FAST выполняет поиск с использованием только индексов, в то время как LOAD_NAME ищет переменную по имени строки. И первый подход в два раза быстрее.