Понимание списка, карта и numpy.vectorize производительность

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

a = [foo(i) for i in xrange(100)]

a = map(foo, range(100))

vfoo = numpy.vectorize(foo)
a = vfoo(range(100))

(Я не забочусь, является ли вывод списком или массивом numpy.)

Существует ли лучший путь?

11
задан hlovdal 26 December 2010 в 14:21
поделиться

4 ответа

Первый мой комментарий заключается в том, что вы должны использовать xrange () или range () во всех ваших примерах. если вы их смешиваете, то сравниваете яблоки и апельсины.

Во-вторых, мнение @Gabe о том, что если у вас много структур данных и они большие, то numpy должен победить в целом ... просто имейте в виду, что в большинстве случаев C быстрее Python, но опять же, большая часть время PyPy быстрее, чем CPython. : -)

что касается вызовов listcomps и map () ... один выполняет 101 вызов функции, а другой - 102. вы не увидите значительной разницы в времени, как показано ниже с использованием модуля timeit , как предложил @Mark:

  • Понимание списка

    $ python -m timeit "def foo (x): pass; [foo (i) for i in range (100)] "
    1000000 циклов, лучшее из 3: 0,216 мксек на цикл
    $ python -m timeit" def foo (x): pass; [foo (i) for i in range (100) ] "
    1000000 циклов, лучшее из 3: 0,21 мксек на цикл
    $ python -m timeit" def foo (x): pass; [foo (i) for i in range (100)] "
    1000000 циклов, лучшее из 3: 0,212 мксек на цикл

  • map () вызов функции

    $ python -m timeit "def foo (x): pass; map (foo, range (100))"
    1000000 циклов, лучшее из 3: 0,216 мксек на цикл
    $ python -m timeit "def foo (x): pass; map (foo, range (100))"
    1000000 циклов, лучшее из 3: 0,214 мксек на цикл
    $ python -m timeit "def foo (x): pass; map (foo, range (100))"
    1000000 циклов, лучшее из 3: 0.215 мкс на цикл

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

в таких случаях я настоятельно рекомендую вместо этого использовать выражения генератора. genexps не создает весь список в памяти ... это более удобный для памяти, ленивый итеративный способ перебора элементов. лучшая часть заключается в том, что его синтаксис почти идентичен синтаксису listcomps:

a = (foo(i) for i in range(100))

вдоль строк большего количества итераций измените все вызовы range () на xrange () для оставшиеся выпуски 2.x затем переключите их обратно на range () при переносе на Python 3, поскольку xrange () заменяет и переименовывается в range () . : -)

9
ответ дан 3 December 2019 в 01:44
поделиться

Самое быстрое понимание списка, затем карта, затем numpy на моей машине. Код numpy на самом деле немного медленнее, чем два других, но эта разница будет намного меньше, если вы используете numpy.arange вместо диапазона (или xrange), как я делал в случаях, перечисленных ниже. Кроме того, если вы используете psyco, понимание списка ускоряется, в то время как два других для меня замедляются. Я также использовал большие массивы чисел, чем в вашем коде, и моя функция foo просто вычисляла квадратный корень. Вот несколько типичных случаев.

Без psyco:

list comprehension: 47.5581952455 ms
map: 51.9082732582 ms
numpy.vectorize: 57.9601876775 ms

С psyco:

list comprehension: 30.4318844993 ms
map: 96.4504427239 ms
numpy.vectorize: 99.5858691538 ms

Я использовал Python 2.6.4 и модуль timeit.

Основываясь на этих результатах, я бы сказал, что, вероятно, на самом деле не имеет значения, какой из них вы выберете для инициализации. Я бы, вероятно, выбрал numpy one или понимание списка в зависимости от скорости, но в конечном итоге вы должны позволить тому, что вы делаете с массивом впоследствии, направлять ваш выбор.

3
ответ дан 3 December 2019 в 01:44
поделиться
  • Почему вы оптимизируете это? Вы написали рабочий, протестировали код, затем проверили свой алгоритм , профилировали код и обнаружили, что его оптимизация будет иметь эффект? Вы делаете это в глубоком внутреннем цикле, в котором, как вы выяснили, проводите свое время? Если нет, не беспокойтесь.

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

  • Вы говорите, что не имеет значения, получите ли вы массив numpy или список , но если вы выполняете такую ​​микрооптимизацию, это имеет значение , поскольку они будут работать по-другому, когда вы будете использовать их позже. Попытаться понять это может быть непросто, так что, надеюсь, эта проблема окажется спорной и преждевременной.

  • Обычно лучше просто использовать правильный инструмент для работы для ясности, удобочитаемости и так далее. Мне редко бывает трудно выбрать между этими вещами.

    • Если бы мне понадобились массивы numpy, я бы использовал их. Я бы использовал их для хранения больших однородных массивов или многомерных данных. Я использую их много, но редко там, где мне кажется, что мне хотелось бы использовать список.
      • Если бы я использовал их, я бы сделал все возможное, чтобы написать свои функции уже векторизовано , поэтому мне не пришлось использовать numpy.vectorize .Например, times_five ниже можно использовать в массиве numpy без оформления.
    • Если бы у меня не было причин использовать numpy, то есть если бы я не решал числовые математические задачи, не использовал специальные функции numpy, не хранил многомерные массивы или что-то еще ... {{1} }
      • Если бы у меня была уже существующая функция , я бы использовал карту . Вот для чего это нужно.
      • Если бы у меня была операция, которая помещалась бы в небольшое выражение, и мне не нужна была бы функция, я бы использовал понимание списка.
      • Если бы я просто хотел выполнить операцию для всех случаев, но на самом деле не нужно было сохранять результат, я бы использовал простой цикл for.
      • Во многих случаях я бы на самом деле использовал map и ленивые эквиваленты перечисленных пониманий: itertools.imap и выражения генератора. В некоторых случаях это может уменьшить использование памяти в n раз, а иногда и избежать выполнения ненужных операций.

Если все-таки окажется, что именно здесь кроются проблемы с производительностью, то сделать все правильно - непросто. Это очень , когда люди выбирают неправильный футляр для игрушки для решения своих реальных проблем. Хуже того, очень часто люди создают на его основе глупые общие правила.

Рассмотрим следующие случаи (timeme.py размещен ниже).

python -m timeit "from timeme import x, times_five; from numpy import vectorize" "vectorize(times_five)(x)"
1000 loops, best of 3: 924 usec per loop

python -m timeit "from timeme import x, times_five" "[times_five(item) for item in x]"
1000 loops, best of 3: 510 usec per loop

python -m timeit "from timeme import x, times_five" "map(times_five, x)"
1000 loops, best of 3: 484 usec per loop

Наивный наблюдатель мог бы заключить, что карта является наиболее эффективной из этих опций, но ответ все равно «это зависит от обстоятельств».Рассмотрите возможность использования преимуществ инструментов, которые вы используете: понимание списков позволяет избежать определения простых функций; numpy позволяет вам векторизовать вещи на C, если вы делаете правильные вещи.

python -m timeit "from timeme import x, times_five" "[item + item + item + item + item for item in x]"
1000 loops, best of 3: 285 usec per loop

python -m timeit "import numpy; x = numpy.arange(1000)" "x + x + x + x + x"
10000 loops, best of 3: 39.5 usec per loop

Но это еще не все, это еще не все. Рассмотрим силу изменения алгоритма. Это может быть еще более драматичным.

python -m timeit "from timeme import x, times_five" "[5 * item for item in x]"
10000 loops, best of 3: 147 usec per loop

python -m timeit "import numpy; x = numpy.arange(1000)" "5 * x"
100000 loops, best of 3: 16.6 usec per loop

Иногда изменение алгоритма может быть даже более эффективным. Это будет становиться все более и более эффективным по мере увеличения числа.

python -m timeit "from timeme import square, x" "map(square, x)"
10 loops, best of 3: 41.8 msec per loop

python -m timeit "from timeme import good_square, x" "map(good_square, x)"
1000 loops, best of 3: 370 usec per loop

И даже сейчас все это может иметь мало отношения к вашей реальной проблеме. Похоже, что numpy настолько хорош, если вы можете использовать его правильно, но у него есть свои ограничения: ни один из этих примеров numpy не использовал реальные объекты Python в массивах. Это усложняет то, что должно быть сделано; даже много. А что, если нам удастся использовать типы данных C? Они менее надежны, чем объекты Python. Они не допускают значения NULL. Целые числа переполнены. Вы должны проделать дополнительную работу, чтобы получить их. Они статически типизированы. Иногда эти вещи оказываются проблемами, даже неожиданными.

Итак, вы идете: окончательный ответ. «Это зависит».


# timeme.py

x = xrange(1000)

def times_five(a):
    return a + a + a + a + a

def square(a):
    if a == 0:
        return 0

    value = a
    for i in xrange(a - 1):
        value += a
    return value

def good_square(a):
    return a ** 2
18
ответ дан 3 December 2019 в 01:44
поделиться

Если выполнение самой функции требует значительного времени, не имеет значения, как вы отображаете ее вывод в массив. Однако, как только вы начнете разбираться в массивах из миллионов чисел, numpy может сэкономить значительный объем памяти.

7
ответ дан 3 December 2019 в 01:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: