Почему этот генэкспс работает хуже, чем понимание списка? - PullRequest
7 голосов
/ 01 февраля 2010

Я пытался найти самый быстрый способ подсчета количества элементов в списке, соответствующих определенному фильтру. В этом случае выясняем, сколько нечетных чисел в списке.

При этом я был удивлен результатами сравнения понимания списка с эквивалентным выражением генератора:

python -m timeit -s "L = xrange(1000000)" "sum([1 for i in L if i & 1])"
10 loops, best of 3: 109 msec per loop

python -m timeit -s "L = xrange(1000000)" "sum(1 for i in L if i & 1)"
10 loops, best of 3: 125 msec per loop

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

Что делает genexp, который заставляет его работать медленнее, чем listcomp, который создает новый список с 1 миллионом элементов ...?

(Кстати, самый быстрый способ, который я нашел, был: x = 1; len(filter(x.__and__, L)). И да, я знаю, что написание такого кода убивает котят, я делаю это ради удовольствия)

Ответы [ 2 ]

15 голосов
/ 01 февраля 2010

Когда доступна практически неограниченная память (что неизменно будет иметь место в крошечных тестах, хотя часто не в реальных проблемах! -), списки будут иметь тенденцию превосходить генераторы, потому что они могут быть выделены только один раз, в одном "большом" связка "(без фрагментации памяти и т. д.), в то время как генераторы требуют (внутренне) дополнительных усилий, чтобы избежать такого подхода" большой связки ", сохраняя состояние стекового фрейма для возобновления выполнения.

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

3 голосов
/ 01 февраля 2010

Из того, что я помню, кадр генератора должен быть активирован для каждого результата, тогда как для понимания списка используется один кадр активации. Дополнительные затраты на сжатие списка - это дополнительные затраты памяти - в вашем случае ссылки на int. Отношение вполне может измениться, если каждый элемент является новым экземпляром и использует больше памяти.

обновление: после тестирования все изменилось

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum([oint(1) for i in L if i & 1])" 
10 loops, best of 3: 414 msec per loop

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum(oint(1) for i in L if i & 1)" 
10 loops, best of 3: 392 msec per loop
...