Как ускорить вывод консоли Python
Обновлено: 21.11.2024
Эта страница посвящена различным советам и приемам, которые помогут повысить производительность ваших программ Python. Всякий раз, когда информация поступает от кого-то еще, я пытался определить источник.
Python существенно изменился с тех пор, как я впервые написал свою страницу о "быстром python" примерно в 1996 году, а это означает, что некоторые порядки будут изменены. Я перенес его на вики Python в надежде, что другие помогут его поддерживать.
Вы всегда должны тестировать эти советы с вашим приложением и конкретной версией реализации Python, которую вы собираетесь использовать, а не просто слепо соглашаться с тем, что один метод быстрее другого. Дополнительные сведения см. в разделе профилирования.
Также новинкой, поскольку это было первоначально написано, являются такие пакеты, как Cython, Pyrex, Psyco, Weave, Shed Skin и PyInline, которые могут значительно повысить производительность вашего приложения, упрощая передачу кода, критичного для производительности, на C или машинный язык.< /p>
Другие версии
Обзор: оптимизируйте то, что нужно оптимизировать
- Правильно.
- Проверьте правильность.
- Профиль, если медленно.
- Оптимизировать.
- Повторить с 2.
Некоторые оптимизации составляют хороший стиль программирования, поэтому их следует изучать по мере изучения языка. Примером может служить перенос вычисления значений, которые не изменяются внутри цикла, за пределы цикла.
Выберите правильную структуру данных
Сортировка
Сортировка списков основных объектов Python, как правило, довольно эффективна. Метод sort для списков принимает необязательную функцию сравнения в качестве аргумента, который можно использовать для изменения поведения сортировки. Это довольно удобно, хотя и может значительно замедлить ваши сортировки, так как функция сравнения будет вызываться много раз. В Python 2.4 вместо этого следует использовать ключевой аргумент встроенной сортировки, которая должна быть самым быстрым способом сортировки.
Только если вы используете более старые версии Python (до 2.4), применим следующий совет Гвидо ван Россума:
Альтернативный способ ускорить сортировку – создать список кортежей, первый элемент которого – это ключ сортировки, который будет правильно сортироваться с использованием сравнения по умолчанию, а второй элемент — исходный элемент списка. Это так называемое преобразование Шварца, также известное как DecorateSortUndecorate (DSU).
Предположим, например, что у вас есть список кортежей, которые вы хотите отсортировать по n-му полю каждого кортежа. Это сделает следующая функция.
Сопоставление поведения с текущим методом сортировки списка (сортировка на месте) также легко достигается:
Вот пример использования:
От Тима Делани
Начиная с Python 2.3 сортировка гарантированно стабильна.
(точнее, он стабилен в CPython 2.3 и гарантированно стабилен в Python 2.4)
Python 2.4 добавляет необязательный ключевой параметр, который значительно упрощает использование преобразования:
Обратите внимание, что исходный элемент никогда не используется для сортировки, только возвращенный ключ — это эквивалентно выполнению:
Конкатенация строк
Точность этого раздела оспаривается в отношении более поздних версий Python. В CPython 2.5 конкатенация строк выполняется довольно быстро, хотя это может не относиться к другим реализациям Python. Обсуждение см. в разделе ConcatenationTestCode.
Строки в Python неизменяемы. Этот факт часто подкрадывается и кусает начинающих программистов Python за зад. Неизменяемость дает некоторые преимущества и недостатки. В столбце «плюс» строки могут использоваться в качестве ключей в словарях, а отдельные копии могут совместно использоваться несколькими привязками переменных. (Python автоматически разделяет одно- и двухсимвольные строки.) В минус-столбце вы не можете сказать что-то вроде «заменить все буквы «а» на «б» в любой заданной строке. Вместо этого вам нужно создать новую строку с нужными свойствами. Такое постоянное копирование может привести к значительной неэффективности программ Python.
Вместо этого используйте s = "".join(list)". Первое является очень распространенной и катастрофической ошибкой при построении больших строк. Точно так же, если вы генерируете биты строки последовательно вместо:
Более того, для удобочитаемости (это не имеет ничего общего с эффективностью, кроме вашей как программиста) используйте подстановку по словарю:
Последние два будут намного быстрее, особенно если они нагромождаются на выполнение множества CGI-скриптов, и их будет легче модифицировать при загрузке. Кроме того, медленный способ делать вещи стал медленнее в Python 2.0 с добавлением в язык расширенных сравнений. Теперь виртуальной машине Python требуется намного больше времени, чтобы понять, как объединить две строки. (Не забывайте, что Python выполняет поиск всех методов во время выполнения.)
Циклы
Python поддерживает несколько циклических конструкций. Чаще всего используется оператор for. Он перебирает элементы последовательности, присваивая каждому из них переменную цикла.Если тело вашего цикла простое, накладные расходы интерпретатора самого цикла for могут составлять значительную часть накладных расходов. Вот где функция карты удобна. Вы можете думать о map как о for, перемещенном в код C. Единственное ограничение состоит в том, что "тело цикла" map должно быть вызовом функции. Помимо синтаксического преимущества понимания списков, они часто работают так же или даже быстрее, чем эквивалентное использование map.
Вот простой пример. Вместо того, чтобы перебирать список слов и преобразовывать их в верхний регистр:
вы можете использовать map, чтобы передать цикл из интерпретатора в скомпилированный код C:
Списковые включения также были добавлены в Python версии 2.0. Они обеспечивают синтаксически более компактный и более эффективный способ написания приведенного выше цикла for:
Выражения генератора были добавлены в Python версии 2.4. Они функционируют примерно так же, как генераторы списков или map, но позволяют избежать накладных расходов, связанных с генерацией всего списка сразу. Вместо этого они возвращают объект-генератор, который можно повторять побитно:
Подходящий метод зависит от используемой версии Python и характеристик данных, с которыми вы работаете.
Гвидо ван Россум написал гораздо более подробное (и краткое) исследование оптимизации циклов, которое, безусловно, стоит прочитать.
Избегайте точек.
Предположим, вы не можете использовать map или понимание списка? Вы можете застрять с циклом for. Пример с циклом for имеет еще одну неэффективность. И newlist.append, и word.upper являются ссылками на функции, которые пересчитываются каждый раз в цикле. Исходный цикл можно заменить на:
Эту технику следует использовать с осторожностью. Его становится сложнее поддерживать, если цикл большой. Если вы не очень хорошо знакомы с этим фрагментом кода, вам придется просмотреть определения append и upper.
Локальные переменные
Последнее ускорение, доступное нам для не-map версии цикла for, заключается в использовании локальных переменных везде, где это возможно. Если приведенный выше цикл приведен как функция, append и upper становятся локальными переменными. Python обращается к локальным переменным намного эффективнее, чем к глобальным переменным.
В то время, когда я писал это, я использовал Pentium 100 МГц с BSDI. Я получил следующее время для преобразования списка слов в /usr/share/dict/words (38 470 слов на тот момент) в верхний регистр:
Инициализация элементов словаря
Предположим, вы создаете словарь частот слов и уже разбили текст на список слов. Вы можете выполнить что-то вроде:
За исключением первого раза, каждый раз, когда встречается слово, проверка оператора if завершается неудачно. Если вы считаете большое количество слов, многие из них, вероятно, будут встречаться несколько раз. В ситуации, когда инициализация значения произойдет только один раз, а увеличение этого значения будет происходить много раз, дешевле использовать оператор try:
Важно перехватить ожидаемое исключение KeyError и не использовать условие except по умолчанию, чтобы избежать попыток восстановления после исключения, которое вы действительно не можете обработать с помощью операторов в попытаться.
Третья альтернатива стала доступна с выпуском Python 2.x. Словари теперь имеют метод get(), который возвращает значение по умолчанию, если нужный ключ не найден в словаре. Это упрощает цикл:
Когда я изначально писал этот раздел, были явные ситуации, когда один из первых двух подходов был быстрее. Кажется, что все три подхода теперь показывают одинаковую производительность (в пределах 10 % друг от друга), более или менее независимые от свойств списка слов.
Кроме того, если значение, хранящееся в словаре, является объектом или (изменяемым) списком, вы также можете использовать метод dict.setdefault, например,
Вы можете подумать, что это избавляет от необходимости дважды искать ключ. На самом деле это не так (даже в Python 3.0), но, по крайней мере, двойной поиск выполняется в C.
Еще один вариант — использовать класс defaultdict:
Заголовок отчета об импорте
Операторыimport можно выполнять где угодно. Часто полезно размещать их внутри функций, чтобы ограничить их видимость и/или сократить время первоначального запуска. Хотя интерпретатор Python оптимизирован, чтобы не импортировать один и тот же модуль несколько раз, многократное выполнение оператора импорта может серьезно повлиять на производительность в некоторых обстоятельствах.
doit2 будет работать намного быстрее, чем doit1, несмотря на то, что ссылка на строковый модуль является глобальной в doit2. Вот сеанс интерпретатора Python, запущенный с использованием Python 2.3 и нового модуля timeit, который показывает, насколько второй быстрее первого:
Строковые методы появились в языке в Python 2.0. Они предоставляют версию, которая полностью исключает импорт и работает еще быстрее:
Вот доказательство от timeit:
Приведенный выше пример явно несколько надуман, но общий принцип верен.
Обратите внимание, что добавление импорта в функцию может ускорить первоначальную загрузку модуля, особенно если импортируемый модуль может не потребоваться. Как правило, это случай «ленивой» оптимизации — избегание работы (импорт модуля, который может быть очень дорогим), пока вы не убедитесь, что он необходим.
Это существенная экономия только в тех случаях, когда модуль вообще не был бы импортирован (из любого модуля) -- если модуль уже загружен (как это бывает со многими стандартными модулями, такими как string или re), отказ от импорта ничего не сэкономит. Чтобы узнать, какие модули загружены в систему, загляните в sys.modules.
Хороший способ отложенного импорта:
Таким образом, модуль email будет импортирован только один раз, при первом вызове parse_email().
Агрегация данных
Накладные расходы на вызов функции в Python относительно высоки, особенно по сравнению со скоростью выполнения встроенной функции. Это настоятельно предполагает, что, когда это уместно, функции должны обрабатывать агрегаты данных. Вот надуманный пример, написанный на Python.
Вот доказательство в пудинге с использованием интерактивного сеанса:
Даже написанный на Python, второй пример работает примерно в четыре раза быстрее, чем первый. Если бы doit был написан на C, разница, вероятно, была бы еще больше (замена цикла Python for на цикл C for, а также удаление большинство вызовов функций).
Делать что-то реже
Интерпретатор Python выполняет некоторые периодические проверки. В частности, он решает, разрешать ли выполнение другому потоку и запускать ли ожидающий вызов (обычно вызов, установленный обработчиком сигнала). Большую часть времени делать нечего, поэтому выполнение этих проверок при каждом проходе цикла интерпретатора может замедлить работу. В модуле sys есть функция setcheckinterval, которую вы можете вызвать, чтобы сообщить интерпретатору, как часто выполнять эти периодические проверки. До выпуска Python 2.3 значение по умолчанию было равно 10. В версии 2.3 это значение было увеличено до 100. Если вы не работаете с потоками и не ожидаете перехвата большого количества сигналов, установка большего значения может улучшить интерпретатор. производительность, иногда существенно.
Python — это не C
Это также не Perl, Java, C++ или Haskell. Будьте осторожны при передаче своих знаний о том, как другие языки работают в Python. Для демонстрации служит простой пример:
Теперь рассмотрим похожие программы на C (показана только добавленная версия):
и время выполнения:
Обратите внимание, что в Python есть значительное преимущество в добавлении числа к самому себе вместо умножения его на два или сдвига влево на один бит. В C на всех современных компьютерных архитектурах каждая из трех арифметических операций преобразуется в одну машинную инструкцию, которая выполняется за один цикл, поэтому не имеет значения, какую из них вы выберете.
Обычный «тест», который часто выполняют новые программисты Python, заключается в переводе общепринятой идиомы Perl
в код Python, который выглядит примерно так
и использовать его, чтобы сделать вывод, что Python должен быть намного медленнее, чем Perl. Как неоднократно указывали другие, Python медленнее Perl для одних вещей и быстрее для других. Относительная производительность также часто зависит от вашего опыта работы с двумя языками.
Используйте xrange вместо range
Этот раздел больше не применяется, если вы используете Python 3, где range теперь предоставляет итератор для диапазонов произвольного размера и где xrange больше не существует.< /p>
В Python есть два способа получить диапазон чисел: range и xrange. Большинство людей знают о range из-за его очевидного названия. xrange, расположенный ближе к концу алфавита, гораздо менее известен.
xrange – это объект генератора, в основном эквивалентный следующему коду Python 2.3:
За исключением того, что он реализован на чистом C.
xrange имеет ограничения. В частности, он работает только с int; вы не можете использовать long или float (они будут преобразованы в int, как показано выше).
Однако это экономит кучу памяти, и если вы не сохраните полученные объекты где-то, в каждый момент времени будет существовать только один полученный объект. Разница в следующем: когда вы вызываете range, он создает list, содержащий столько чисел (int, long или float). Все эти объекты создаются одновременно, и все они существуют одновременно. Это может быть проблемой, когда число чисел велико.
xrange, с другой стороны, немедленно создает никаких чисел — только сам объект диапазона. Числовые объекты создаются только тогда, когда вы тянете за генератор, например. прокручивая его. Например:
И по этой причине код запускается мгновенно. Если вы подставите здесь range, Python заблокируется; он будет слишком занят выделением числовых объектов sys.maxint (около 2,1 миллиарда на обычном ПК), чтобы делать что-либо еще. В конце концов, ему не хватит памяти и он выйдет.
В версиях Python до 2.2 объекты xrange также поддерживали такие оптимизации, как быстрое тестирование членства (i в xrange(n)). Эти функции были удалены в версии 2.2 из-за ненадобности.
Переназначение функций во время выполнения
Скажем, у вас есть функция
И предположим, что эта функция вызывается откуда-то еще много раз.
Что ж, в вашем чеке будет оператор if, который замедляет работу все время, кроме первого раза, так что вы можете сделать следующее:
Ну, этот пример довольно неадекватен, но если оператор if представляет собой довольно сложное выражение (или что-то с большим количеством точек), вы можете избавить себя от его оценки, если знаете, что оно будет истинным только в первый раз. .
Код профилирования
Первый шаг к ускорению работы программы – изучение узких мест. Едва ли имеет смысл оптимизировать код, который никогда не выполняется или уже работает быстро. Я использую два модуля, чтобы найти горячие точки в моем коде, профиль и трассировку. В более поздних примерах я также использую модуль timeit, который является новым в Python 2.3.
Советы в этом разделе устарели. Альтернативы приведенным ниже подходам см. в отдельном документе по профилированию.
Профилирование
В дистрибутив Python включен ряд модулей профилирования. Использовать один из них для профилирования выполнения набора функций довольно просто. Предположим, что ваша основная функция называется main, не принимает аргументов и вы хотите выполнить ее под управлением модуля profile. В простейшей форме вы просто выполняете
При возврате main() модуль profile распечатает таблицу вызовов функций и времени выполнения. Вывод можно настроить с помощью класса Stats, включенного в модуль. Начиная с Python 2.4, profile позволяет также профилировать время, затрачиваемое встроенными модулями Python и функциями в модулях расширения.
Несколько более подробное описание профилирования с использованием модулей profile и pstats можно найти здесь (архивная версия):
Модуль cProfile
Модуль `cProfile` — это альтернатива profile, написанная на C, которая обычно работает намного быстрее. Он использует тот же интерфейс.
Модуль трассировки
Модуль трассировки является побочным продуктом модуля профиля, который я изначально написал для выполнения некоторого грубого тестового покрытия на уровне операторов. Он был сильно изменен несколькими другими людьми с тех пор, как я выпустил свою первоначальную грубую работу. Начиная с Python 2.0, вы должны найти trace.py в каталоге Tools/scripts дистрибутива Python. Начиная с Python 2.3, он находится в стандартной библиотеке (каталог Lib). Вы можете скопировать его в локальный каталог bin и установить разрешение на выполнение, а затем выполнить его напрямую. Его легко запустить из командной строки, чтобы отследить выполнение целых скриптов:
В Python 2.4 работать еще проще. Просто выполните python -m trace.
Отдельной документации нет, но вы можете запустить "трассировку pydoc", чтобы просмотреть встроенную документацию.
Визуализация результатов профилирования
RunSnakeRun – это инструмент с графическим интерфейсом Майка Флетчера, который визуализирует дамп профилей из cProfile с помощью квадратных карт. Вызовы функций/методов можно сортировать по различным критериям, а исходный код может отображаться вместе с визуализацией и статистикой вызовов. В настоящее время (апрель 2016 г.) RunSnakeRun поддерживает только Python 2.x, поэтому он не может загружать данные профиля, сгенерированные программами Python 3.
Пример использования:
Gprof2Dot – это инструмент на основе Python, который может преобразовывать результаты профилирования в график, который можно преобразовать в изображение PNG или SVG.
Типичный сеанс профилирования с помощью Python 2.5 выглядит следующим образом (на более старых платформах вам нужно будет использовать настоящий скрипт вместо параметра -m):
PyCallGraph pycallgraph — это модуль Python, который создает графы вызовов для программ Python. Он создает файл PNG, показывающий вызовы функций модуля и их связь с другими вызовами функций, количество вызовов функции и время, затраченное на эту функцию.
PyProf2CallTree — это скрипт, помогающий визуализировать данные профилирования, собранные с помощью модуля Python cProfile с помощью графического анализатора дерева вызовов kcachegrind.
ProfileEye – это браузерный интерфейс для gprof2dot, использующий d3.js для очистки визуальной информации.
SnakeViz – это браузерный визуализатор данных профиля.
PythonSpeed/PerformanceTips (последнее редактирование EmeryBerger 2020-04-26 20:58:33)
Меня всегда поражало/разочаровывало то, сколько времени уходит на простой вывод на терминал с оператором печати. После недавней мучительно медленной регистрации я решил разобраться в этом и был очень удивлен, обнаружив, что почти все время уходит на ожидание обработки терминалом результатов.
Можно ли как-то ускорить запись в стандартный вывод?
Я написал сценарий (' print_timer.py' внизу этого вопроса), чтобы сравнить время при записи 100 тыс. строк в стандартный вывод, в файл и при перенаправлении стандартного вывода на /dev/null . Вот результат синхронизации:
Вау. Чтобы убедиться, что python не делает что-то за кулисами, например, распознает, что я переназначил стандартный вывод на /dev/null или что-то в этом роде, я сделал перенаправление вне скрипта.
Так что это не трюк с питоном, это просто терминал. Я всегда знал, что сброс вывода в /dev/null ускоряет процесс, но никогда не думал, что это так важно!
Меня поражает, насколько медленный телетайп. Как могло случиться, что запись на физический диск НАМНОГО быстрее, чем запись на «экран» (предположительно операция с оперативной памятью), и фактически такая же быстрая, как просто сброс в мусор с помощью /dev/null?
Эта ссылка рассказывает о том, как терминал будет блокировать ввод-вывод, чтобы он мог "анализировать [входные данные], обновлять свой буфер кадров, обмениваться данными с X-сервером для прокрутки окна и т. д." эм>. но я не до конца понимаю. Что может длиться так долго?
Я полагаю, что выхода нет (кроме более быстрой реализации tty?), но я все равно спрошу.
ОБНОВЛЕНИЕ: прочитав некоторые комментарии, я задался вопросом, насколько сильно размер моего экрана влияет на время печати, и это имеет определенное значение. Приведенные выше действительно медленные цифры относятся к моему терминалу Gnome, увеличенному до 1920x1200. Если я уменьшу его до минимума, то получу.
Это, безусловно, лучше (~4x), но не меняет моего вопроса. Это только дополняет мой вопрос, поскольку я не понимаю, почему рендеринг экрана терминала должен замедлять запись приложения в стандартный вывод. Почему моя программа должна ждать продолжения рендеринга экрана?
Не все ли терминальные/tty-приложения созданы одинаковыми? Мне еще предстоит экспериментировать. Мне действительно кажется, что терминал должен иметь возможность буферизовать все входящие данные, анализировать/рендерить их невидимо и отображать только самый последний фрагмент, который виден в текущей конфигурации экрана, с разумной частотой кадров. Таким образом, если я могу записать + fsync на диск примерно за 0,1 секунды, терминал должен быть в состоянии выполнить ту же операцию в чем-то в этом порядке (возможно, с несколькими обновлениями экрана, пока он это делает).
Я все еще надеюсь, что есть параметр tty, который можно изменить со стороны приложения, чтобы сделать это поведение более удобным для программиста. Если это строго проблема терминального приложения, то, возможно, она даже не относится к StackOverflow?
Python гибок, но может быть медленным. Давайте ускорим его.
Python — один из моих любимых языков для работы. Он прост в освоении, имеет отличный выбор библиотек с открытым исходным кодом и имеет чрезвычайно активное и полезное сообщество. Добавьте ко всему этому гибкость Python, и вы легко поймете, почему так много разработчиков по всему миру приняли Python.
Я также предпочитаю Python для работы с данными. R никогда не казался мне очень организованным, и доступ Python к библиотекам машинного обучения исторически был намного лучше, чем R. Однако, когда дело доходит до работы с большими объемами данных, Python может быть очень медленным. По сравнению с такими языками, как C и C++, Python иногда может показаться слишком медленным. К счастью, есть замечательные библиотеки и встроенные функции, которые могут ускорить код Python.
Список понятий
Списковые включения — это очень питоновский способ создания списка. Допустим, мы хотим возвести в квадрат все четные числа меньше 100 000 и добавить их в список. Если бы мы использовали цикл for, мы могли бы сделать что-то вроде этого:
Это работает, но мы могли бы сэкономить время и немного почистить наш код, используя так называемое понимание списка.
Как мы видим, Python позволяет нам создавать список внутри оператора []. Это даже позволяет нам добавить условный оператор для проверки четных чисел.
Хотя это явно экономит место, почему оно ускоряет работу? Основная причина заключается в том, что мы строим список по запросу без необходимости вызывать append() на каждой итерации цикла. В примере с циклом мы загружаем атрибут append, а затем вызываем его как функцию на каждой итерации цикла.
Воспроизведение списков можно использовать в удивительном количестве мест во многих базах кода, что может обеспечить кумулятивный значительный прирост производительности. Просто они обычно менее читабельны, поэтому было бы неплохо оставить комментарий к более сложному включению списка.
Используйте встроенные функции
Многие встроенные функции Python написаны на C, что делает их намного быстрее, чем чистое решение на Python. Возьмите очень простую задачу суммирования большого количества чисел. Мы могли бы перебирать каждое число, суммируя по мере продвижения. Однако в Python есть функция sum(), которая обеспечивает невероятный прирост производительности.
В Python доступно множество встроенных функций, которые вы можете найти здесь.
Вызовы функций стоят дорого
Вызовы функций в Python обходятся дорого. Хотя разделение кода на функции часто является хорошей практикой, бывают случаи, когда вы должны быть осторожны при вызове функций из цикла. Лучше выполнять итерацию внутри функции, чем выполнять итерацию и вызывать функцию на каждой итерации. Взгляните на следующий пример, где мы хотим создать список значений в квадрате в диапазоне от 1 до 1 000 000.
Мы видим, что второй метод был значительно быстрее при прочих равных условиях, за исключением использования функции для возведения значения в квадрат. У функций много накладных расходов, поэтому для таких простых задач, как эта, они могут добавить много времени по отношению к общему количеству.
Отложенный импорт модулей
Традиционно файл Python импортирует все необходимые библиотеки вверху. Это означает, что всякий раз, когда файл импортируется или запускается как скрипт, импортируются все эти библиотеки. Если есть модули, которые нужны только в определенных ситуациях, нам не всегда нужно их импортировать. Вместо этого мы можем импортировать их только тогда, когда это необходимо, и избежать накладных расходов на их загрузку, когда они не нужны.
Воспользуйтесь преимуществами Numpy
Numpy — это высокооптимизированная библиотека, созданная на C. Почти всегда быстрее перенести математику в Numpy, чем полагаться на интерпретатор Python. Numpy также имеет сверхэффективные структуры данных, предназначенные для хранения матричных данных, которые требуют меньше накладных расходов, чем встроенные структуры данных Python.
Если бы мы хотели возвести каждый элемент в список в квадрат, мы могли бы сделать это следующим образом:
Это работает нормально, но давайте посмотрим, насколько быстрее Numpy сможет это сделать.
Вау! Это намного быстрее, чем использование списка в Python. Numpy открывает всевозможные возможности для научных вычислений с помощью Python. Если вы работаете с большим количеством матриц, стоит стать мастером Numpy. Помните встроенную функцию sum()? Используя Numpy, мы также можем снизить его производительность:
Numpy может значительно повысить производительность математических вычислений в Python, однако вы должны быть очень осторожны, чтобы придерживаться структур данных и методов Numpy для достижения такого уровня оптимизации. Просто неправильное создание массива в этой ситуации сводит на нет прирост производительности:
Это намного медленнее, чем простое использование встроенных методов Python, что связано с затратами времени на преобразование между структурами данных Python и Numpy. Итак, просто имейте в виду, что, хотя Numpy хорошо работает со структурами данных Python, он намного быстрее работает только с Numpy.
Попробуйте многопроцессорную обработку
Многопроцессорность может значительно увеличить производительность скрипта Python, но ее может быть сложно правильно реализовать по сравнению с другими методами, упомянутыми в этом посте.
Большинство современных потребительских компьютеров имеют от 2 до 16 ядер. Python обычно ограничивается одним ядром при обработке кода, но использование многопроцессорной библиотеки позволяет нам использовать более одного ядра. В задачах, сильно связанных с ЦП, разделение работы между несколькими процессорами действительно может ускорить процесс.
В этой ситуации у меня есть действительно большая трехмерная матрица, состоящая из тысяч спутниковых изображений, наложенных друг на друга. Мне нужно применить фильтр к каждому пикселю во всем временном ряду. Это может занять значительное время для повторения каждого пикселя изображения, поэтому мы можем использовать многопроцессорность, чтобы разделить работу между всеми доступными процессорами на машине. Для этого я просто разбиваю куб на столько фрагментов, сколько доступно процессоров, и применяю функцию к каждому фрагменту. Когда все куски готовы, я просто соединяю их, чтобы получить конечный продукт.
Помните, что многопроцессорность не может компенсировать неоптимизированный код. Часто я оставляю многопроцессорность в качестве последнего шага после того, как код, который я запускаю, становится настолько быстрым, насколько я могу его сделать. Здесь мы видим, что, несмотря на распределение работы по 24 ядрам, мы добились увеличения скорости только в 3 раза. Это связано с накладными расходами на разделение/повторное объединение данных и управление многопроцессорным пулом.
Поскольку многопроцессорные пулы имеют приличную нагрузку, они, как правило, лучше всего работают в таких ситуациях, когда у меня нет возможности еще больше ускорить этот код. Вместо дальнейшей оптимизации filterfunc я разделяю проблему на большее количество рабочих. Представьте, что Apple нужно удвоить количество производимых телефонов. Они могли бы потратить больше времени на обучение своих рабочих, чтобы производить их более эффективно, или они могли бы просто нанять массу людей с улицы.Скорее всего, существует баланс этих двух факторов, который позволит достичь оптимальных результатов.
Будьте осторожны с громоздкими библиотеками
Одним из преимуществ Python по сравнению с другими языками программирования является богатый выбор сторонних библиотек, доступных разработчикам. По сути, у каждого пакета Python есть список зависимостей, которые он использует для достижения своих целей. Из-за огромного количества библиотек Python почти всегда доступно несколько библиотек, которые могут выполнять одни и те же задачи.
Но мы не всегда можем учитывать размер библиотеки, которую используем в качестве зависимости. Пару месяцев назад я работал над функцией AWS Lambda, которая извлекала данные о маршрутах из базы данных, а затем соединяла близлежащие маршруты в цикл туда и обратно. Я столкнулся с ограничением размера для среды AWS Lambda из-за слишком большого количества зависимостей Python. Просмотрев свои зависимости одну за другой, я понял, что использую scikit-learn для одной простой задачи, которую, вероятно, мог бы выполнить с помощью чего-то другого. Оказалось, что Cartopy поддерживает очень похожую функцию в гораздо меньшей общей библиотеке, благодаря чему моя среда подходит для AWS Lambda.
Помимо ограничений по размеру, уменьшение размера вашей среды повышает скорость при каждом ее использовании. Если вы можете уменьшить количество зависимостей, вы также снизите риск возникновения конфликтов зависимостей в будущем.
Избегайте глобальных переменных
Обычно в программе по информатике мы довольно рано узнаём, что глобальные переменные в Python — не лучшая практика. Обычно предпочтительнее использовать локальные переменные, чтобы лучше отслеживать область действия и использование памяти. Но помимо использования памяти, Python также немного быстрее извлекает локальные переменные, чем глобальные. Поэтому по возможности лучше избегать глобальных переменных.
Попробуйте несколько решений
На примерах Numpy мы увидели, что время, потраченное на рассмотрение структур данных и методов, которые вы используете, может существенно повлиять на скорость вашего кода. Когда мы впервые начинаем изучать Python, приятно иметь возможность решить проблему несколькими способами. Но часто есть решение, которое быстрее остальных, а иногда все сводится к использованию другого метода или структуры данных.
Возьмите подсчет вхождений букв в длинном фрагменте текста. collections.Counter — это, как правило, очень быстрый способ подсчета уникальных элементов в структуре данных. Однако в Python есть метод, который лучше оптимизирован для работы именно со строками.
Здесь мы видим, насколько быстрее встроенный метод str.count справляется с этой конкретной задачей. Это связано с тем, что Counter() является универсальным инструментом, который можно использовать для подсчета гораздо большего, чем просто символов в строке, в то время как str.count сильно оптимизирован для поиска символов в строке. Это означает, что str.count работает с лежащим в основе символом C, и ему не приходится заниматься повторением строк Python.
Подумайте о своих структурах данных
Python имеет тенденцию абстрагироваться от некоторых структур данных, которые многие из нас изучают на начальных курсах C++, но они все еще существуют. Поскольку Python занимается построением многих распространенных структур данных, я думаю, что многие люди склонны забывать об относительных преимуществах каждого из них.
Например, наборы и словари в Python имеют производительность поиска O(1), поскольку они используют хэш-таблицы. Большинство пользователей Python по умолчанию используют списки, однако бывают ситуации, когда словарь или набор имеют больше смысла.
Поиск в словаре или наборе выполняется безумно быстро, но поиск в списках занимает время, пропорциональное длине списка. Однако наборы и словари не поддерживают порядок. Если вам важен порядок ваших данных, вы не можете использовать словари или наборы.
В качестве примера предположим, что мы обрабатываем миллионы чисел и сохраняем результаты. Мы хотим создать таблицу поиска, которая позволит нам быстро увидеть, было ли уже вычислено значение. С точки зрения скорости список будет O (n), а словарь будет O (1). Однако потребление памяти словарями намного больше, чем списками, поскольку они также хранят хеш-таблицу. Если мы добавляем новые элементы в список на лету, нам, вероятно, придется использовать словарь. Но если мы используем его только как таблицу поиска, возможно, сортировка списка и использование двоичного поиска могут быть быстрее в зависимости от типов данных. Двоичный поиск в отсортированном списке занимает O(log n), что, скорее всего, будет быстрее только для целых данных или данных с плавающей запятой.
Надеемся, что некоторые из этих советов помогут вам писать более быстрый код Python в будущем. Python может быть не самым быстрым языком программирования в нашем распоряжении, но он один из самых гибких. Чем быстрее мы сможем заставить Python работать, тем ближе мы подойдем к гибкому и чрезвычайно производительному языку.
Фокус Python заключается в использовании высокооптимизированных функций, созданных с использованием C.Хотя всегда заманчиво реализовать собственное решение проблемы, стоит ознакомиться с невероятными уже доступными инструментами.
Консоль Python позволяет выполнять команды и сценарии Python построчно, аналогично тому, как вы работаете с Python Shell.
Работа с консолью Python
Консоль появляется в виде окна инструментов каждый раз, когда вы выбираете соответствующую команду в меню "Инструменты". Вы можете назначить ярлык для открытия консоли Python: нажмите Ctrl+Alt+S , перейдите к Keymap , укажите ярлык для Главное меню | Инструменты | Python или консоль отладки .
Основной причиной использования консоли Python в PyCharm является использование преимуществ основных функций IDE, таких как завершение кода, анализ кода и быстрые исправления.
Вы можете использовать клавиши со стрелками вверх и вниз для просмотра истории выполненных команд и повторения нужных команд. Для предварительного просмотра значений переменных, вычисленных в ходе выполнения, щелкните и проверьте список «Специальные переменные».
Консоль доступна для всех типов интерпретаторов Python и виртуальных сред, как локальных, так и удаленных.
Предварительный просмотр переменной в виде массива
Если ваши переменные представляют собой пустые массивы или кадры данных, вы можете просмотреть их как массив в отдельном окне. Чтобы попробовать, выполните одно из следующих действий:
Нажмите ссылку «Просмотреть как массив» / «Просмотреть как кадр данных»:
В контекстном меню переменной выберите «Просмотр как массив» / «Просмотр как кадр данных»:
Переменная будет открыта на вкладке "Данные" окна SciView.
Запуск исходного кода из редактора в консоли
Откройте файл в редакторе и выберите исполняемый фрагмент кода.
В контекстном меню выделения выберите Выполнить выделение в консоли или нажмите Alt+Shift+E :
Если выбор не выбран, команда изменится на строку «Выполнить» в консоли. Выберите эту команду из контекстного меню или нажмите Alt+Shift+E. Строка с символом вставки загружается в консоль Python и запускается.
Наблюдайте за выполнением выбора кода:
По умолчанию консоль Python выполняет команды Python, используя интерпретатор Python, определенный для проекта. Однако вы можете назначить альтернативный интерпретатор Python.
Настройка параметров консоли Python
В диалоговом окне «Параметры/настройки» ( Ctrl+Alt+S ) выберите «Сборка, выполнение, развертывание | Консоль | Консоль Python.
Выберите любой доступный интерпретатор из списка интерпретаторов Python. Обратите внимание, что здесь нельзя ввести новый интерпретатор. Если вы хотите придумать новый интерпретатор, вам нужно сначала его создать.
При необходимости нажмите ссылку «Настроить интерпретаторы», чтобы просмотреть список установленных пакетов и добавить новые.
Обратите внимание на код в области начального сценария. Он содержит скрипт, который будет выполняться после того, как вы откроете консоль Python. Используйте его для предварительного написания некоторых необходимых команд Python.
При работе с несколькими скриптами Python может потребоваться выполнение каждого из них в отдельной консоли Python.
Запустите несколько консолей Python
Нажмите, чтобы добавить новую консоль Python.
По умолчанию каждая консоль имеет имя Python Console с индексом. Чтобы консоль отражала выполняемый вами сценарий, щелкните правой кнопкой мыши вкладку консоли, выберите "Переименовать консоль" и введите любое осмысленное имя.
Все команды, которые вы запускаете в консоли Python, выполняются одна за другой. Если для выполнения команд требуется значительное время, вы можете просмотреть очередь выполнения и управлять ею.
Управление очередью выполнения команд
Нажмите на панели инструментов консоли, чтобы открыть очередь.
В диалоговом окне "Очередь команд консоли Python" просмотрите список команд. При необходимости нажмите, чтобы удалить команду из очереди.
Обратите внимание, что после выполнения команда исчезает из очереди. Для предварительного просмотра всех ранее выполненных команд просмотрите историю консоли ().
Читайте также: