Предположим, у меня в очень плотном цикле есть следующее:
a = func(x)
b = func2(a)
Переменная a больше нигде не используется.
Компилирует ли Python присвоение a автоматически, или требуется время, чтобы каждый раз выполнять присвоение переменной? Другими словами, идентичен ли этот код или он немного быстрее из-за отсутствия присвоения a?
b = func2(func(x))
Такое же поведение для Python2.7 и Python3?
Это похоже на вопрос, на который вы могли бы ответить, просто попробовав его самостоятельно (например, используя модуль время).
@larsks Я читал, что timeit несколько ненадежен для таких небольших временных различий. Все ответы ниже, в которых он используется, показывают различия порядка наносекунд, что неубедительно и может быть просто шумом. Ответы на байт-код - это ответ, который я искал (и раньше я не знал о dis)






Такие запросы можно легко проверить с помощью timeit. Вот результаты с Python2.7.
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
1000000 loops, best of 3: 0.29 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
1000000 loops, best of 3: 0.284 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
1000000 loops, best of 3: 0.285 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
1000000 loops, best of 3: 0.283 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
1000000 loops, best of 3: 0.294 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
1000000 loops, best of 3: 0.286 usec per loop
И это показывает согласованные результаты с другими ответами, в которых описывается использование замечательного модуля dis.
Вероятно, было бы разумнее поместить создание f1 и f2 в блок настройки, а не внутри цикла синхронизации.
Согласовано. Нет необходимости инициализировать для каждой итерации.
Итак, используя очень забавный модуль dis, мы можем посмотреть на фактический байт-код, который генерируется из предоставленного вами кода Python. Для простоты я заменил func и func2 встроенными функциями (int и float).
Итак, наш исходник выглядит так:
def assign():
a = int()
b = float(a)
По сравнению с упрощенной версией:
def simple():
b = float(int())
А затем, начиная с интерпретатора cpython 2.7, мы можем увидеть байт-коды, сгенерированные функцией assign:
dis.dis(assign)
2 0 LOAD_GLOBAL 0 (int)
3 CALL_FUNCTION 0
6 STORE_FAST 0 (a)
3 9 LOAD_GLOBAL 1 (float)
12 LOAD_FAST 0 (a)
15 CALL_FUNCTION 1
18 STORE_FAST 1 (b)
21 LOAD_CONST 0 (None)
24 RETURN_VALUE
Как вы можете видеть, для удаления ненужной промежуточной переменной нет никакой оптимизации, что приводит к дополнительным 2 инструкциям (STORE_FAST a, LOAD_FAST a) по сравнению с байт-кодами для упрощенного `простого метода:
dis.dis(simple)
2 0 LOAD_GLOBAL 0 (float)
3 LOAD_GLOBAL 1 (int)
6 CALL_FUNCTION 0
9 CALL_FUNCTION 1
12 STORE_FAST 0 (b)
15 LOAD_CONST 0 (None)
18 RETURN_VALUE
То же самое для интерпретатора CPython для Python 3.5 и для интерпретатора pypy для Python 2.7.
Используйте модуль dis для сравнения байт-кода: похоже, что второй метод производит меньше операции
import dis
print(dis.dis('a=f(2);b=g(a)'))
print(dis.dis('b=g(f(2))'))
>>>
1 0 LOAD_NAME 0 (f)
2 LOAD_CONST 0 (2)
4 CALL_FUNCTION 1
6 STORE_NAME 1 (a)
8 LOAD_NAME 2 (g)
10 LOAD_NAME 1 (a)
12 CALL_FUNCTION 1
14 STORE_NAME 3 (b)
16 LOAD_CONST 1 (None)
18 RETURN_VALUE
None
1 0 LOAD_NAME 0 (g)
2 LOAD_NAME 1 (f)
4 LOAD_CONST 0 (2)
6 CALL_FUNCTION 1
8 CALL_FUNCTION 1
10 STORE_NAME 2 (b)
12 LOAD_CONST 1 (None)
14 RETURN_VALUE
None
В основном то же самое, что и ответ @MatthewStory, но показывает другой способ использования dis.dis - я оставлю его, если не получу отрицательных ответов.
Фактическое время будет зависеть от функций func() и func2(). Не лучший пример, но ниже приведен быстрый (и грязный) тестовый код:
import time
def func(x):
return 5
def func2(a):
return 10
t0 = time.time()
x = 10
for i in range(1,10000):
a = func(x)
b = func2(a)
t1 = time.time()
print("Time 1: ", t1-t0)
t2 = time.time()
x = 10
for i in range(1,10000):
b = func2(func(x))
t3 = time.time()
print("Time 2: ", t3-t2)
Вывод приведенного выше кода:
Time 1: 0.0029211044311523438
Time 2: 0.002785921096801758
Так что да, реализация, в которой мы избегаем назначения a, немного быстрее в Pyhton 3.
Почему это должно зависеть от конкретных функций?
Извините, я имел в виду, что фактическое время будет зависеть от содержимого функций. Отредактировал ответ.
не уверен, но без присваивания можно было бы немного быстрее