Оптимизация в Python - что можно, чего нельзя и практические правила

Я читал этот Почта, а потом наткнулся на код, который был:

jokes=range(1000000)
domain=[(0,(len(jokes)*2)-i-1) for i in range(0,len(jokes)*2)]

Я подумал, не лучше ли вычислить значение len (шутки), когда оно выходит за рамки понимания списка?

Ну, я попробовал и рассчитал три кода

jv@Pioneer:~$ python -m timeit -s 'jokes=range(1000000);domain=[(0,(len(jokes)*2)-i-1) for i in range(0,len(jokes)*2)]'
10000000 loops, best of 3: 0.0352 usec per loop
jv@Pioneer:~$ python -m timeit -s 'jokes=range(1000000);l=len(jokes);domain=[(0,(l*2)-i-1) for i in range(0,l*2)]'
10000000 loops, best of 3: 0.0343 usec per loop
jv@Pioneer:~$ python -m timeit -s 'jokes=range(1000000);l=len(jokes)*2;domain=[(0,l-i-1) for i in range(0,l)]'
10000000 loops, best of 3: 0.0333 usec per loop

Наблюдая за предельной разницей 2,55% между первым и вторым, я подумал - это первое понимание списка

domain=[(0,(len(jokes)*2)-i-1) for i in range(0,len(jokes)*2)]

оптимизирован внутри Python? или 2,55% - это достаточно большая оптимизация (учитывая, что len (шутки) = 1000000)?

Если это так - каковы другие неявные / внутренние оптимизации в Python?

Что такое developer's rules of thumb for optimization in Python?

Редактировать1: Поскольку большинство ответов - «не оптимизируйте, сделайте это позже, если будет медленно», и я получил несколько советов и ссылок от Triptych и Ali A для делать. Немного изменю вопрос и прошу не.

Можем ли мы получить некоторый опыт от людей, которые столкнулись с проблемой «медлительность», в чем заключалась проблема и как ее исправить?

Edit2: Для тех, у кого его нет, вот интересно читать

Edit3: Неправильное использование timeit, о котором идет речь, пожалуйста, смотрите ответ dF's для правильного использования и, следовательно, времени для трех кодов.

прочтите мой комментарий к ответу, который вы приняли

John Machin 08.05.2010 03:09
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
8
1
9 588
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

Что касается практических правил разработчика для оптимизации в Python, они такие же, как и для всех языков.

  1. Не оптимизируйте.
  2. (дополнительно) Оптимизировать позже.

len для списков - O (1). Нет необходимости сканировать весь список, чтобы найти длину, потому что размер списка сохраняется для поиска. Но, видимо, все же немного быстрее извлечь его в локальную переменную.

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

Прочтите это: Советы по скорости / производительности Python

Кроме того, в вашем примере общее время настолько мало, что погрешность перевешивает любую фактическую разницу в скорости.

Ответ принят как подходящий

Вы неправильно используете timeit: аргумент -s (setup) - это оператор, который должен быть выполнен один раз изначально, поэтому на самом деле вы просто тестируете пустой оператор. Ты хочешь сделать

$ python -m timeit -s "jokes=range(1000000)" "domain=[(0,(len(jokes)*2)-i-1) for i in range(0, len(jokes)*2)]"
10 loops, best of 3: 1.08 sec per loop
$ python -m timeit -s "jokes=range(1000000)" "l=len(jokes);domain=[(0,(l*2)-i-1) for i in range(0, l*2)]"
10 loops, best of 3: 908 msec per loop
$ python -m timeit -s "jokes=range(1000000)" "l=len(jokes*2);domain=[(0,l-i-1) for i in range(0, l)]"
10 loops, best of 3: 813 msec per loop

Хотя ускорение по-прежнему невелико, оно более значительное (16% и 25% соответственно). Так как это не усложняет код, эта простая оптимизация, вероятно, того стоит.

Чтобы ответить на фактический вопрос ... обычное эмпирическое правило в Python заключается в том, чтобы

  1. При кодировании отдайте предпочтение простому и удобочитаемому коду, а не оптимизации.

  2. Профилируйте свой код (profile / cProfile и pstats - ваши друзья), чтобы выяснить, что вам нужно оптимизировать (обычно такие вещи, как жесткие циклы).

  3. В крайнем случае, повторно реализуйте их как расширения C, что значительно упрощается с помощью таких инструментов, как пирекс и Cython.

Одна вещь, на которую следует обратить внимание: по сравнению со многими другими языками, вызовы функций в Python относительно дороги, поэтому оптимизация в вашем примере имела значение, хотя len - это O (1) для списков.

@dF: Просмотрите edit2 вопроса, в котором говорится о дорогостоящих вызовах функций и прочем.

JV. 31.12.2008 22:41

-1 НЕУДАЧА Ваш 3-й прогон выполняет l=len(jokes*2) (создаёт список вдвое больше, получает его длину, отбрасывает его) вместо того, что было у OP: l=len(jokes)*2. Также не пробовал xrange вместо range. [(0, j) for j in xrange(2*len(jokes)-1,-1,-1)] тоже не пробовал

John Machin 08.05.2010 03:07

Это относится ко всему программированию, а не только к Python:

  1. Профиль
  2. Определите узкие места
  3. Оптимизировать

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

И, возможно, наиболее важно то, что модульные тесты помогут вам на самом деле.

шаг -1: выяснить правильный алгоритм. шаг 0: напишите чистый код

Mr Fooz 31.12.2008 22:48

Самое важное, что нужно сделать, - это написать идиоматичный, понятный и красивый код Python. Многие общие задачи уже находятся в stdlib, поэтому вам не нужно переписывать более медленную версию. (Я имею в виду именно строковые методы и инструменты itertools.) Также широко используйте встроенные контейнеры Python. dict, например, "оптимизировал сопли", и сказано, что код Python, использующий dicts, будет быстрее, чем простой C!

Если это еще не так, есть несколько приемов, которые вы можете использовать, но это также сигнал о том, что вам, вероятно, следует переложить некоторую работу на модули расширения C.

Что касается понимания списков: CPython может сделать несколько оптимизаций по сравнению с обычными циклами аккумуляторов. А именно, код операции LIST_APPEND выполняет добавление к собственной операции списка.

+1 за упоминание stdlib. Я до сих пор помню, как сам написал groupby, а затем нашел itertools.groupby. Но мне интересно, что "код Python, использующий dicts, будет быстрее, чем простой C!"

JV. 31.12.2008 22:35

@JV Я уверен, что это будет быстрее, чем любое домашнее решение на C.

Benjamin Peterson 31.12.2008 22:51

Can we have some experiences from people who faced the 'slowness', what was the problem and how it was corrected?

Проблема заключалась в медленном извлечении данных в приложении с графическим интерфейсом. Я получил 50-кратное ускорение, добавив указатель к таблице, и был повсеместно провозглашен героем и спасителем.

я просил не о медлительности Python, но все же герой остается героем. :) ...просто шучу'.

JV. 01.01.2009 00:04

У меня есть программа, которая анализирует файлы журналов и создает хранилище данных. Типичный запуск включает около 200 миллионов строк файла журнала и выполняется большую часть дня. Стоит оптимизировать!

Поскольку это синтаксический анализатор, и при этом он анализирует довольно изменчивый, своеобразный и ненадежный текст, существует около 100 регулярных выражений, заранее тщательно подготовленных re.compiled () и применяемых к каждой из 200 миллионов строк файла журнала. Я был почти уверен, что они были моим узким местом, и размышлял, как исправить эту ситуацию. У меня были идеи: с одной стороны, делать меньше и красивее RE; с другой - больше и проще; вроде того.

Я профилировал с помощью CProfile и смотрел на результат в "runnake".

Обработка RE занимала всего около 10% времени выполнения кода. Это не то!

Фактически, большая квадратная капля на дисплее Runnake мгновенно сообщила мне, что около 60% моего времени было потрачено на одно из тех печально известных «однострочных изменений», которые я добавил однажды, исключив непечатаемые символы (которые иногда появляются но всегда представляю что-то настолько фальшивое, что меня это действительно не волнует). Это сбивало с толку мой синтаксический анализ и выдачу исключений, о которых я беспокоюсь, потому что это остановило мой день анализа файла журнала.

line = ''.join([c for c in line if curses.ascii.isprint(c) ])

Вот и все: эта строка касается каждого байт каждой из этих 200 миллионов строк (а длина строк составляет в среднем пару сотен байтов). Неудивительно, что это 60% моего времени выполнения!

Теперь я знаю, что есть более эффективные способы справиться с этим, например str.translate (). Но такие строки редки, и я все равно не забочусь о них, и в конечном итоге они вызывают исключение: теперь я просто ловлю исключение в нужном месте и пропускаю строку. Вуаля! программа работает примерно в 3 раза быстрее, мгновенно!

Итак, профилирование

  1. примерно за одну секунду высветил, где проблема на самом деле была
  2. отвлек мое внимание от ошибочного предположения о том, где была проблема (что могло быть еще большей расплатой)

Другие вопросы по теме