Почему лямбда-функция в понимании списка медленнее, чем в карте? - PullRequest
4 голосов
/ 21 января 2020

Я сделал тест

%timeit list(map(lambda x:x,range(100000)))
%timeit [(lambda x:x)(i) for i in range(100000)]

дает

29 ms ± 4.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
51.2 ms ± 3.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Почему в python лямбда-функция медленнее в понимании списка, чем в карте?

Ответы [ 2 ]

6 голосов
/ 21 января 2020

Поскольку в вашем первом фрагменте кода lambda создается только один раз и выполняется 100000 раз, в то время как на втором он создается и выполняется в каждой итерации.

Если честно, я удивлен, что разница не даже больше, но ваши два времени должны расти дальше друг от друга, наибольший размер итерируемой длины.


Заметьте, что даже если вы измените lambda на встроенную функцию, не нужно сначала создавать, а только искать, вы все равно получаете ту же тенденцию:

> py -m timeit "list(map(float, range(10000)))"
200 loops, best of 5: 1.78 msec per loop

> py -m timeit "[float(x) for x in range(10000)]"
100 loops, best of 5: 2.35 msec per loop

Улучшения можно достичь, связав функцию с переменной, но схема list(map()) еще быстрее.

> py -m timeit "r=float;[r(x) for x in range(10000)]"
100 loops, best of 5: 1.93 msec per loop

Что касается , почему привязка к локальной переменной быстрее, вы можете найти больше информации здесь .

2 голосов
/ 21 января 2020

TLDR: понимание каждый раз оценивает все свое выражение. map оценивает свое выражение один раз и каждый раз применяет результат .


Выражение, передаваемое в map и значения, обрабатываются по-разному. Важные части того, что делают map и понимания, можно перевести следующим образом:

def map(func: 'callable', iterable):
    for item in iterable:
        yield func(item)

def comprehension(expr: 'code', iterable):
    for item in iterable:
        yield eval(expr)

Важное отличие состоит в том, что map уже получает функцию , тогда как comprehension получает выражение .

Теперь lambda x:x - это выражение, которое оценивает как функцию.

>>> co = compile('lambda x: x', '<stackoverflow>', 'eval')
>>> co
<code object <module> at 0x105d1c810, file "<stackoverflow>", line 1>
>>> eval(co)
<function __main__.<lambda>(x)>

В частности, оценивая выражение для получения функции - это действие, которое требует времени.

Поскольку map и понимания ожидают разных вещей, lambda передается им по-разному. То, что делает интерпретатор, может быть расширено с помощью явного eval / compile:

>>> list(map(eval(compile('lambda x: x', '<stackoverflow>', 'eval')), range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(comprehension(compile('(lambda x: x)(item)', '<stackoverflow>', 'eval'), range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Важное отличие должно быть теперь ясно:

  • Для map выражение оценивается один раз , прежде чем оно будет передано в map. Для каждого элемента map вызывает результирующую функцию.
  • Для comprehension выражение передается как неоцененное. Для каждого элемента comprehension создает функцию , затем вызывает результирующую функцию.

Важно отметить, что map не всегда быстрее, чем понимание. map выигрывает от создания / поиска функции один раз. Однако вызовы функций являются дорогостоящими, и для понимания не требуется использовать функцию.

Как правило, если выражение использует только имена, локальные для понимания, оно выполняется быстрее.

In [302]: %timeit list(map(lambda i:i,range(100000)))
     ...: %timeit [i for i in range(100000)]
8.86 ms ± 38.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
4.49 ms ± 35.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Если само выражение имеет для динамического выполнения дорогостоящей операции, map не приносит пользы.

In [302]: %timeit list(map(lambda i: (lambda x:x)(i),range(100000)))
     ...: %timeit [(lambda x:x)(i) for i in range(100000)]
19.7 ms ± 39.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
15.9 ms ± 43.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...