Почему inline if/else быстрее, чем .get() в Python?

Я просмотрел код раньше, и разработчик написал inline if/else, а не get(), чтобы извлечь элемент из списка, если он существует (иначе это значение по умолчанию). Я решил spring некоторый код timeit на repl и был довольно смущен результатом. if/else занимает 1/3 времени get().

Вот код замены, а ниже - код в repl, а также результат для потомков:

import timeit

D = {"a": 1, "b": 2, "c": 3}

def ef(): return D['a'] if 'a' in D else 1

def gt(): return D.get('a', 1)

print "gt1", timeit.timeit(gt, number=10000)
print "ef1", timeit.timeit(ef, number=10000)
print "ef2", timeit.timeit(ef, number=10000)
print "gt2", timeit.timeit(gt, number=10000)

и результаты:

gt1 0.0659999847412
ef1 0.0239999294281
ef2 0.0249998569489
gt2 0.0539999008179

и визуализацию 10 итераций вышеуказанных вызовов timeit, где результат был умножен на 10000 для целей представления

visual of 10 iterations

Ответ 1

Путь D.get() включает в себя поиск атрибута и вызов метода:

>>> import dis
>>> D = {"a": 1, "b": 2, "c": 3}
>>> def gt(): return D.get('a', 1)
... 
>>> dis.dis(gt)
  1           0 LOAD_GLOBAL              0 (D)
              3 LOAD_ATTR                1 (get)
              6 LOAD_CONST               1 ('a')
              9 LOAD_CONST               2 (1)
             12 CALL_FUNCTION            2
             15 RETURN_VALUE        

Поиск атрибута (LOAD_ATTR) особенно замедляет работу.

Если вы удалите поиск атрибута (и дайте тесту in локальное работать), поле будет выровнено:

>>> def gt_fast(D_get=D.get): return D_get('a', 1)
... 
>>> def ef_fast(D=D): return D['a'] if 'a' in D else 1
... 
>>> timeit.timeit(gt_fast)
0.2174091339111328
>>> timeit.timeit(ef_fast)
0.2139298915863037