Ответ 1
Следующие аргументы фактически применимы только к CPython, для других реализаций Python он может быть совершенно другим. Вы действительно сказали, что ваш вопрос касается CPython, но, тем не менее, мне кажется важным понять, что эти подробные вопросы почти всегда зависят от деталей реализации, которые могут отличаться для разных реализаций и могут даже отличаться между различными версиями CPython (например, CPython 2.7 может быть совершенно другим, но это может быть CPython 3.5)!
Задержки
Прежде всего, я не могу воспроизвести различия в 15% или даже 20%. На моем компьютере разница составляет около ~ 10%. Это еще меньше, когда вы меняете lambda
поэтому ей не нужно искать add
из глобальной области (как уже указывалось в комментариях, вы можете передать функцию add
качестве аргумента по умолчанию функции, чтобы поиск выполнялся в локальной области).
from functools import partial
def add(x, y, z, a):
return x + y + z + a
def max_lambda_default(lst):
return max(lst , key=lambda a, add=add: add(10, 20, 30, a))
def max_lambda(lst):
return max(lst , key=lambda a: add(10, 20, 30, a))
def max_partial(lst):
return max(lst , key=partial(add, 10, 20, 30))
Я фактически сравнивал их:
from simple_benchmark import benchmark
from collections import OrderedDict
arguments = OrderedDict((2**i, list(range(2**i))) for i in range(1, 20))
b = benchmark([max_lambda_default, max_lambda, max_partial], arguments, "list size")
%matplotlib notebook
b.plot_difference_percentage(relative_to=max_partial)
Возможные объяснения
Очень трудно найти точную причину разницы. Однако есть несколько возможных вариантов, предполагая, что у вас есть версия CPython с скомпилированным модулем _functools
(все версии настольных версий CPython, которые я использую, имеют это).
Как вы уже выяснили, partial
часть Python будет значительно медленнее.
-
partial
реализуется в C и может вызывать функцию напрямую - без промежуточного слоя Python 1. С другой стороны,lambda
должна выполнить вызов уровня Python для "захваченной" функции. -
partial
фактически знает, как аргументы совпадают. Таким образом, он может создавать аргументы, которые передаются функции более эффективно (он просто конкатенает сохраненный кортеж аргументов в переданный в аргументе tuple) вместо того, чтобы создавать абсолютно новый аргумент tuple. -
В более поздних версиях Python несколько изменений были изменены, чтобы оптимизировать вызовы функций (так называемая оптимизация FASTCALL). У Виктора Стинера есть список связанных запросов на тягу в его блоге, если вы хотите узнать об этом больше.
Вероятно, это повлияет как на
lambda
и наpartial
но опять же, потому чтоpartial
- это функция С, она знает, какой из них вызывать напрямую, не делая этого, какlambda
.
Однако очень важно понять, что для создания partial
есть некоторые накладные расходы. Точка безубыточности для ~ 10 элементов списка, если список короче, тогда lambda
будет быстрее.
Сноски
1 Если вы вызываете функцию из Python, она использует OP-код CALL_FUNCTION
который на самом деле является оболочкой (что я имел в виду с слоем Python) вокруг PyObject_Call*
(или FASTCAL). Но он также включает создание аргумента tuple/dictionary. Если вы вызываете функцию из функции C, вы можете избежать этой тонкой оболочки, напрямую вызвав функции PyObject_Call*
.
В случае, если вы заинтересованы о OP-кодах, вы можете dis
собрать функцию:
import dis
dis.dis("add(10, 20, 30, a)")
1 0 LOAD_NAME 0 (add)
2 LOAD_CONST 0 (10)
4 LOAD_CONST 1 (20)
6 LOAD_CONST 2 (30)
8 LOAD_NAME 1 (a)
10 CALL_FUNCTION 4
12 RETURN_VALUE
Как вы видите, код CALL_FUNCTION
на самом деле там.
Как и в стороне: LOAD_NAME
отвечает за разницы в производительности между lambda_default
и lambda
без умолчания. Это потому, что загрузка имени фактически начинается с проверки локальной области (области действия), в случае add=add
добавить функцию добавления в локальную область, и она может остановиться тогда. Если у вас его нет в локальной области, он будет проверять каждую окружение до тех пор, пока не найдет имя, и он остановится только при достижении глобальной области. И этот поиск выполняется каждый раз, когда вызывается lambda
!