Почему питон медленный язык
Перейти к содержимому

Почему питон медленный язык

  • автор:

AlexKorablev.ru

AlexKorablev.ru

Александр Кораблев о разработке ПО, ИТ-индустрии и Python.

Почему Python такой популярный, если он такой медленный?

Опубликовано 14 December 2015 в Python

На Quora обсуждают почему Python такой популярный, хотя такой медленный. Тема конечно холиварная. Но порассуждать на нее интересно. Ведь Python действительно медленный.

Медленный, если сравнивать его с C/C++ или Java, или C#. Это факт. Python достаточно медленный скриптовый динамический язык программирования. Любая метрика покажет, что программа на C++ будет работать быстрее. Но есть у языков и другая не менее важная метрика: скорость разработки.

Вот тут динамические языки начинают выигрывать. Разрабатывать на Python, Ruby, JS быстрее, чем на Java. А скорость разработки — это прямая экономия для компании. Сегодня разработка продукта заканчивается только, если проект закрывают. В остальных случаях купить еще один инстанс у амазона дешевле, чем увеличить срок разработки какой-либо фичи на пару недель.

В добавок, высокая производительность — не самоцель. Программа должна решать задачу с приемлемой производительностью. Задач где производительность критичная не так много. И даже тогда правильный алгоритм влияет на скорость больше, чем выбор языка реализации.

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

Еще один плюс Python (теперь уже по сравнению «одноклассниками») — его простой и читаемый синтаксис. Я слышал много историй, когда ученые, далекие от программирования, предпочитали делать наброски вычислений на Python. Не из-за наличия SciPy/NumPy, а именно из-за того, что такую программу проще написать и объяснить коллегам. Программистам работающим в командах простой синтаксис то же в плюс.

Конечно «батарейки» важны. За это, пожалуй, любят питон ребята из Data Science. Наверняка, для статистических расчетов и анализа есть более интересные инструменты. Но данные для таких расчетов нужно еще извлечь и подготовить. А потом оказывается, что считать на Python так же удобно.


Возник вопрос? Мне всегда можно написать в Twitter: avkorablev

Понравилась статья? Поделись с друзьями!

Да, Python медленный, но меня это не волнует

Разговоры о снижении производительности ради продуктивности.

Я беру паузу в моём обсуждении asyncio в Python, чтобы поговорить о скорости Python. Позвольте представиться, я — ярый поклонник Python, и использую его везде, где только удаётся. Одна из причин, почему люди выступают против этого языка, — то, что он медленный. Некоторые отказываются даже попробовать на нём поработать лишь из-за того, что «X быстрее». Вот мои мысли на этот счёт.

Производительность более не важна

Раньше программы выполнялись очень долго. Ресурсы процессора и памяти были дорогими, и время работы было важным показателем. А уж сколько платили за электричество! Однако, те времена давно прошли, потому что главное правило гласит:

Оптимизируйте использование своих самых дорогих ресурсов.

Исторически самым дорогим было процессорное время. Именно к этому подводят при изучению информатики, фокусируясь на эффективности различных алгоритмов. Увы, это уже не так — железо сейчас дёшево как никогда, а вслед за ним и время выполнения становится всё дешевле. Самым дорогим же становится время сотрудника, то есть вас. Гораздо важнее решить задачу, нежели ускорить выполнение программы. Повторюсь для тех, кто просто пролистывает статью:

Гораздо важнее решить задачу, нежели ускорить выполнение программы.

Вы можете возразить: «В моей компании заботятся о скорости. Я создаю веб-приложение, в котором все ответы приходят меньше, чем за x миллисекунд» или «от нас уходили клиенты, потому что считали наше приложение слишком медленным». Я не говорю, что скорость работы не важна совсем, я хочу лишь сказать, что она перестала быть самой важной; это уже не самый дорогой ваш ресурс.

Скорость — единственная вещь, которая имеет значение.

Когда вы употребляете слово «скорость» в контексте программирования, скорей всего вы подразумеваете производительность CPU. Но когда ваш CEO употребляет его применительно к программированию, то он подразумевает скорость бизнеса. Самая главная метрика — время выхода на рынок. В конечном счёте неважно насколько быстро работают ваши продукты, не важно на каком языке программирования они написаны или сколько требуется ресурсов для их работы. Единственная вещь, которая заставит вашу компанию выжить или умереть, — это время выхода на рынок. Я говорю больше не о том, насколько быстро идея начнёт приносить доход, а о том, как быстро вы сможете выпустить первую версию продукта. Единственный способ выжить в бизнесе — развиваться быстрее, чем конкуренты. Неважно, сколько у вас идей, если конкуренты реализуют их быстрее. Вы должны быть первыми на рынке, ну или как минимум не отставать. Чуть затормозитесь — и погибните.

Единственный способ выжить в бизнесе — развиваться быстрее, чем конкуренты.

Поговорим о микросервисах

Крупные компании, такие как Amazon, Google или Netflix, понимают важность быстрого развития. Они специально строят свой бизнес так, чтобы быстро вводить инновации. Микросервисы помогают им в этом. Эта статья не имеет никакого отношения к тому, следует ли вам строить на них свою архитектуру, но, по крайней мере, согласитесь с тем, что Amazon и Google должны их использовать. Микросервисы медленны по своей сути. Сама их концепция заключается в том, чтобы использовать сетевые вызовы. Иными словами, вместо вызова функции вы отправляетесь гулять по сети. Что может быть хуже с точки зрения производительности?! Сетевые вызовы очень медленны по сравнению с тактами процессора. Однако, большие компании по-прежнему предпочитают строить архитектуру на основе микросервисов. Я действительно не знаю, что может быть медленнее. Самый большой минус такого подхода — производительность, в то время как плюсом будет время выхода на рынок. Создавая команды вокруг небольших проектов и кодовых баз, компания может проводить итерации и вводить инновации намного быстрее. Мы видим, что даже очень крупные компании заботятся о времени выхода на рынок, а не только стартапы.

Процессор не является вашим узким местом

Если вы пишете сетевое приложение, такое как веб-сервер, скорее всего, процессор не является узким местом вашего приложения. В процессе обработки запроса будет сделано несколько сетевых обращений, например, к базе данных или кешу. Эти сервера могут быть сколь угодно быстрыми, но всё упрётся в скорость передачи данных по сети. Вот действительно классная статья, в которой сравнивается время выполнения каждой операции. В ней автор считает за один такт процессора одну секунду. В таком случае запрос из Калифорнии в Нью-Йорк растянется на 4 года. Вот такая вот сеть медленная. Для примерных расчётов предположим, что передача данных по сети внутри датацентра занимает около 3 мс, что эквивалентно 3 месяцам по нашей шкале отсчёта. Теперь возьмём программу, которая нагружает CPU. Допустим, ей надо 100 000 циклов, чтобы обработать один вызов, это будет эквивалентно 1 дню. Предположим, что мы пишем на языке, который в 5 раз медленнее — это займёт 5 дней. Для тех, кто готов ждать 3 месяца, разница в 4 дня уже не так важна.

В конечном счёте то, что python медленный, уже не имеет значения. Скорость языка (или процессорного времени) почти никогда не является проблемой. Google описал это в своём исследовании. А там, между прочим, говорится о разработке высокопроизводительной системы. В заключении приходят к следующему выводу:

Решение использовать интерпретируемый язык программирования в высокопроизводительных приложениях может быть парадоксальным, но мы столкнулись с тем, что CPU редко когда является сдерживающим фактором; выразительность языка означает, что большинство программ невелики и большую часть времени тратят на ввод-вывод, а не на собственный код. Более того, гибкость интерпретируемой реализации была полезной, как в простоте экспериментов на лингвистическом уровне, так и в предоставлении нам возможности исследовать способы распределения вычислений на многих машинах.

Другими словами:

CPU редко когда является сдерживающим фактором.

Что, если мы всё-таки упираемся в CPU?

Что насчёт таких аргументов: «Всё это прекрасно, но что, если CPU становится узким местом и это начинает сказываться на производительности?» или «Язык x менее требователен к железу, нежели y»? Они тоже имеют место быть, однако вы можете масштабировать приложение горизонтально бесконечно. Просто добавьте серверов, и сервис будет работать быстрее 🙂 Без сомнения, Python более требователен к железу, чем тот же C, но стоимость нового сервера гораздо меньше стоимости вашего времени. То есть дешевле будет докупить ресурсов, а не бросаться каждый раз что-то оптимизировать.

Так что, Python быстрый?

На протяжении всей статьи я твердил, что самое важное — время разработчика. Таким образом, остается открытым вопрос: быстрее ли Python языка X во время разработки? Смешно, но я, Google и кое-кто ещё, могут подтвердить насколько продуктивен Python. Он скрывает так много вещей от вас, помогая сосредоточиться на основной вашей задаче и не отвлекаясь на всякие мелочи. Давайте рассмотрим некоторые примеры из жизни.

По большей части все споры вокруг производительности Python скатываются к сравнению динамической и статической типизации. Думаю, все согласятся, что статически типизированные языки менее продуктивны, но на всякий случай вот хорошая статья, объясняющая почему. Что касается непосредственно Python, то есть хороший отчёт об исследовании, в котором рассматривается, сколько времени потребовалось, чтобы написать код для обработки строк на разных языках.

В представленном выше исследовании Python более чем в 2 раза продуктивнее Java. Многие другие также языки программирования дают похожий результат. Rosetta Code провёл довольно обширное исследование различий изучения языков программирования. В статье они сравнивают python с другими интерпретируемыми языками и заявляют:

Python, в целом, наиболее краток, даже в сравнении с функциональными языками (в среднем в 1,2-1,6 раза короче).

По-видимому, в реализации на Python будет как правило меньше строк кода, чем на каком-либо другом языке. Это может показаться ужасной метрикой, однако несколько исследований, в том числе и упомянутых выше, показывают, что время, затраченное на каждую строку кода, примерно одинаково на каждом языке. Таким образом, чем меньше строк кода, тем больше продуктивность. Даже сам codinghorror (программист на C#) написал статью о том, что Python более продуктивен.

Я думаю, будет справедливо сказать, что Python более продуктивен, чем многие другие языки программирования. В основном это связано с тем, что он поставляется «с батарейками», а также имеет множество сторонних библиотек на все случаи жизни. Вот небольшая статья, сравнивающая Python и X. Если вы мне не верите, можете сами убедиться насколько этот язык программирования прост и удобен. Вот ваша первая программа:

import __hello__

Но что, если скорость действительно важна?

Мои рассуждения выше могли сложить мнение, что оптимизация и скорость выполнения программы вообще не имеют значения. Во многих случаях это не так. Например, у вас есть веб-приложение и некоторый endpoint, который уж очень тормозит. У вас могут быть даже определённые требования на этот счёт. Наш пример строится на некоторых предположениях:

  1. есть некоторый endpoint, который выполняется медленно
  2. есть некоторые метрики, определяющие насколько медленными может быть обработка запросов

Дисклеймер

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

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

Любые улучшения, сделанные где-либо помимо узкого места, являются иллюзией. — Джин Ким.

Если оптимизация не устраняет узкого места приложения, то вы просто потратите впустую своё время и не решите реальную проблему. Вы не должны продолжать разработку, пока не исправите тормоза. Если вы пытаетесь оптимизировать нечто, не зная конкретно что, то вряд ли результат вас удовлетворит. Это и называется «преждевременной оптимизацией» — улучшение производительности без каких-либо метрик. Д. Кнуту часто приписывают следующую цитату, хотя он утверждает, что она не его:

Преждевременная оптимизация — корень всех зол.

Если быть точным, то более полная цитата:

Мы должны забыть об эффективности, скажем, на 97% времени: преждевременная оптимизация — корень всех зол. Однако мы не должны упускать наши возможности в этих критических 3%.

Другими словами, здесь говорится, что большую часть времени вам не нужно думать об оптимизации. Код и так хорош 🙂 А в случае, когда это не так, нужно переписать не более 3% Вас никто не похвалит, если вы сделаете обработку запроса на несколько наносекунд быстрее. Оптимизируйте то, что поддаётся измерению.

Преждевременная оптимизация, как правило, заключается в вызове более быстрых методов или использовании специфичных структур данных из-за их внутренней реализации. В университете нас учили, что если два алгоритма имеют одну асимптотику Big-O, то они эквивалентны. Даже если один из них в 2 раза медленнее. Компьютеры сейчас настолько быстры, что вычислительную сложность пора измерять на большом количестве данных. То есть, если у вас есть две функции O(log n), но одна в два раза медленнее другой, то это не имеет большого значения. По мере увеличения размера данных они обе начинают показывать примерно одно и то же время выполнения. Вот почему преждевременная оптимизация — это корень всего зла; Это тратит наше время и практически никогда не помогает нашей общей производительности.

В терминах Big-O все языки программирования имеют сложность O(n), где n — кол-во строк кода или инструкций. Не имеет значения, насколько медленным будет язык или его виртуальная машина — все они имеют общую асимптоту. В соответствии с этим рассуждением можно сказать, что «быстрый» язык программирования всего лишь преждевременно оптимизированный, причём непонятно по каким метрикам.

Оптимизируя Python

Что мне нравится в Python, так это то, что он позволяет оптимизировать небольшой участок кода за раз. Допустим, у вас есть метод на Python, который вы считаете своим узким местом. Вы оптимизировали его несколько раз, возможно, следуя советам отсюда и отсюда, и теперь пришли к выводу, что уже сам Python является узким местом. Но он имеет возможность вызова кода на C, а это означает, что вы можете переписать этот метод на C, чтобы уменьшить проблему с производительностью. Вы без проблем можете использовать этот метод вместе с остальным кодом.

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

Существует также язык Cython, который является надмножеством Python. Он представляет из себя смесь с типизированным C. Любой код Python является также валидным кодом и на Cython, который транслируется в C. Вы можете смешивать типы C и утиную типизацию. Используя Cython, вы получаете прирост производительности только в узком месте, не переписывая весь остальной код. Так делает, например, EVE Online. Эта MMoRPG использует только Python и Cython для всего стека, проводя оптимизацию только там, где это требуется. Кроме того, есть и другие способы. Например, PyPy — реализация JIT Python, которая может дать вам значительное ускорение во время выполнения долгоживущих приложений (например, веб-сервера), просто путем замены CPython (реализация по умолчанию) на PyPy.

Итак, основные моменты:

  • оптимизируйте использование самого дорогого ресурса — то есть вас, а не компьютера;
  • выбирайте язык/фреймворк/архитектуру, которая позволяет вам разрабатывать продукты как можно быстрее. Не стоит выбирать язык программирования лишь потому, что программы на нём работают быстро;
  • если у вас проблемы с производительностью — определите, где именно;
  • и, скорее всего, это не ресурсы процессора или Python;
  • если это всё же Python (и вы уже оптимизировали алгоритм), реализуйте проблемное место на Cython/C;
  • и побыстрее возвращайтесь к основной работе.
  • python
  • сравнение производительности
  • продуктивность работы
  • почему не любят python

Python — это медленно. Почему?

В последнее время можно наблюдать рост популярности языка программирования Python. Он используется в DevOps, в анализе данных, в веб-разработке, в сфере безопасности и в других областях. Но вот скорость… Здесь этому языку похвастаться нечем. Автор материала, перевод которого мы сегодня публикуем, решил выяснить причины медлительности Python и найти средства его ускорения.

Общие положения

Как Java, в плане производительности, соотносится с C или C++? Как сравнить C# и Python? Ответы на эти вопросы серьёзно зависят от типа анализируемых исследователем приложений. Не существует идеального бенчмарка, но, изучая производительность программ, написанных на разных языках, неплохой отправной точкой может стать проект The Computer Language Benchmarks Game.

Я ссылаюсь на The Computer Language Benchmarks Game уже больше десяти лет. Python, в сравнении с другими языками, такими, как Java, C#, Go, JavaScript, C++, является одним из самых медленных. Сюда входят языки, в которых используется JIT-компиляция (C#, Java), и AOT-компиляция (C, C++), а также интерпретируемые языки, такие, как JavaScript.

Тут мне хотелось бы отметить, что говоря «Python», я имею в виду эталонную реализацию интерпретатора Python — CPython. В этом материале мы коснёмся и других его реализаций. Собственно говоря, здесь мне хочется найти ответ на вопрос о том, почему Python требуется в 2-10 раз больше времени, чем другим языкам, на решение сопоставимых задач, и о том, можно ли сделать его быстрее.

Вот основные теории, пытающиеся объяснить причины медленной работы Python:

  • Причина этого — в GIL (Global Interpreter Lock, глобальная блокировка интерпретатора).
  • Причина в том, что Python — это интерпретируемый, а не компилируемый язык.
  • Причина — в динамической типизации.

GIL

Современные компьютеры обладают многоядерными процессорами, иногда встречаются и многопроцессорные системы. Для того чтобы использовать всю эту вычислительную мощь, операционная система применяет низкоуровневые структуры, называемые потоками, в то время как процессы (например — процесс браузера Chrome) могут запускать множество потоков и соответствующим образом их использовать. В результате, например, если какой-то процесс особенно сильно нуждается в ресурсах процессора, его выполнение может быть разделено между несколькими ядрами, что позволяет большинству приложений быстрее решать встающие перед ними задачи.

Например, у моего браузера Chrome, в тот момент, когда я это пишу, имеется 44 открытых потока. Тут стоит учитывать то, что структура и API системы работы с потоками различается в операционных системах, основанных на Posix (Mac OS, Linux), и в ОС семейства Windows. Операционная система, кроме того, занимается планированием работы потоков.

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

Когда интерпретатор CPython создаёт переменные, он выделяет память, а затем подсчитывает количество существующих ссылок на эти переменные. Эта концепция известна как подсчёт ссылок (reference counting). Если число ссылок равняется нулю, тогда соответствующий участок памяти освобождается. Именно поэтому, например, создание «временных» переменных, скажем, в пределах областей видимости циклов, не приводит к чрезмерному увеличению объёма памяти, потребляемого приложением.

Самое интересное начинается тогда, когда одними и теми же переменными совместно пользуются несколько потоков, а главная проблема тут заключается в том, как именно CPython выполняет подсчёт ссылок. Тут и проявляется действие «глобальной блокировки интерпретатора», которая тщательно контролирует выполнение потоков.

Интерпретатор может выполнять лишь одну операцию за раз, независимо от того, как много потоков имеется в программе.

▍Как GIL влияет на производительность Python-приложений?

Если у нас имеется однопоточное приложение, работающее в одном процессе интерпретатора Python, то GIL никак на производительность не влияет. Если, например, избавиться от GIL, никакой разницы в производительности мы не заметим.

Если же, в пределах одного процесса интерпретатора Python, надо реализовать параллельную обработку данных с применением механизмов многопоточности, и используемые потоки будут интенсивно использовать подсистему ввода-вывода (например, если они будут работать с сетью или с диском), тогда можно будет наблюдать последствия того, как GIL управляет потоками. Вот как это выглядит в случае использования двух потоков, интенсивно нагружающих процессов.

Визуализация работы GIL (взято отсюда)

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

▍Как ведут себя другие интерпретаторы Python?

В PyPy есть GIL, он обычно более чем в 3 раза быстрее, чем CPython.

В Jython нет GIL, так как потоки Python в Jython представлены в виде потоков Java. Такие потоки используют возможности по управлению памятью JVM.

▍Как управление потоками организовано в JavaScript?

Если говорить о JavaScript, то, в первую очередь, надо отметить, что все JS-движки используют алгоритм сборки мусора mark-and-sweep. Как уже было сказано, основная причина необходимости использования GIL — это алгоритм управления памятью, применяемый в CPython.

В JavaScript нет GIL, однако, JS — это однопоточный язык, поэтому в нём подобный механизм и не нужен. Вместо параллельного выполнения кода в JavaScript применяются методики асинхронного программирования, основанные на цикле событий, промисах и коллбэках. В Python есть нечто подобное, представленное модулем asyncio .

Python — интерпретируемый язык

Мне часто приходилось слышать о том, что низкая производительность Python является следствием того, что это — интерпретируемый язык. Подобные утверждения основаны на грубом упрощении того, как, на самом деле, работает CPython. Если, в терминале, ввести команду вроде python myscript.py , тогда CPython начнёт длительную последовательность действий, которая заключается в чтении, лексическом анализе, парсинге, компиляции, интерпретации и выполнении кода скрипта. Если вас интересуют подробности — взгляните на этот материал.

Для нас, при рассмотрении этого процесса, особенно важным является тот факт, что здесь, на стадии компиляции, создаётся .pyc -файл, и последовательность байт-кодов пишется в файл в директории __pycache__/ , которая используется и в Python 3, и в Python 2.

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

В результате, большую часть времени (если только вы не пишете код, который запускается лишь один раз) Python занимается выполнением готового байт-кода. Если сравнить это с тем, что происходит в Java и в C#, окажется, что код на Java компилируется в «Intermediate Language», и виртуальная машина Java читает байт-код и выполняет его JIT-компиляцию в машинный код. «Промежуточный язык» .NET CIL (это то же самое, что .NET Common-Language-Runtime, CLR), использует JIT-компиляцию для перехода к машинному коду.

В результате, и в Java и в C# используется некий «промежуточный язык» и присутствуют похожие механизмы. Почему же тогда Python показывает в бенчмарках гораздо худшие результаты, чем Java и C#, если все эти языки используют виртуальные машины и какие-то разновидности байт-кода? В первую очередь — из-за того, что в .NET и в Java используется JIT-компиляция.

JIT-компиляция (Just In Time compilation, компиляция «на лету» или «точно в срок») требует наличия промежуточного языка для того, чтобы позволить осуществлять разбиение кода на фрагменты (кадры). Системы AOT-компиляции (Ahead Of Time compilation, компиляция перед исполнением) спроектированы так, чтобы обеспечить полную работоспособность кода до того, как начнётся взаимодействие этого кода с системой.

Само по себе использование JIT не ускоряет выполнение кода, так как на выполнение поступают, как и в Python, некие фрагменты байт-кода. Однако JIT позволяет выполнять оптимизации кода в процессе его выполнения. Хороший JIT-оптимизатор способен выявить наиболее нагруженные части приложения (такую часть приложения называют «hot spot») и оптимизировать соответствующие фрагменты кода, заменив их оптимизированными и более производительными вариантами, чем те, что использовались ранее.

Это означает, что когда некое приложение снова и снова выполняет некие действия, подобная оптимизация способна значительно ускорить выполнение таких действий. Кроме того, не забывайте о том, что Java и C# — это языки со строгой типизацией, поэтому оптимизатор может делать о коде больше предположений, способствующих улучшению производительности программ.

JIT-компилятор есть в PyPy, и, как уже было сказано, эта реализация интерпретатора Python гораздо быстрее, чем CPython. Сведения, касающиеся сравнения разных интерпретаторов Python, можно найти в этом материале.

▍Почему в CPython не используется JIT-компилятор?

У JIT-компиляторов есть и недостатки. Один из них — время запуска. CPython и так запускается сравнительно медленно, а PyPy в 2-3 раза медленнее, чем CPython. Длительное время запуска JVM — это тоже известный факт. CLR .NET обходит эту проблему, запускаясь в ходе загрузки системы, но тут надо отметить, что и CLR, и та операционная система, в которой запускается CLR, разрабатываются одной и той же компанией.

Если у вас имеется один процесс Python, который работает длительное время, при этом в таком процессе имеется код, который может быть оптимизирован, так как он содержит интенсивно используемые участки, тогда вам стоит серьёзно взглянуть на интерпретатор, имеющий JIT-компилятор.

Однако, CPython — это реализация интерпретатора Python общего назначения. Поэтому, если вы разрабатываете, с использованием Python, приложения командной строки, то необходимость длительного ожидания запуска JIT-компилятора при каждом запуске этого приложения сильно замедлит работу.

CPython пытается обеспечить поддержку как можно большего количества вариантов использования Python. Например, существует возможности подключения JIT-компилятора к Python, правда, проект, в рамках которого реализуется эта идея, развивается не особенно активно.

В результате можно сказать, что если вы, с помощью Python, пишете программу, производительность которой может улучшиться при использовании JIT-компилятора — используйте интерпретатор PyPy.

Python — динамически типизированный язык

В статически типизированных языках, при объявлении переменных, необходимо указывать их типы. Среди таких языков можно отметить C, C++, Java, C#, Go.

В динамически типизированных языках понятие типа данных имеет тот же смысл, но тип переменной является динамическим.

a = 1 a = "foo"

В этом простейшем примере Python сначала создаёт первую переменную a , потом — вторую с тем же именем, имеющую тип str , и освобождает память, которая была выделена первой переменной a .

Может показаться, что писать на языках с динамической типизацией удобнее и проще, чем на языках со статической типизацией, однако, такие языки созданы не по чьей-то прихоти. При их разработке учтены особенности работы компьютерных систем. Всё, что написано в тексте программы, в итоге, сводится к инструкциям процессора. Это означает, что данные, используемые программой, например, в виде объектов или других типов данных, тоже преобразуются к низкоуровневым структурам.

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

Отсутствие необходимости указывать тип переменной при её объявлении — это не та особенность языка, которая делает Python медленным. Архитектура языка позволяет сделать динамическим практически всё, что угодно. Например, во время выполнения программы можно заменять методы объектов. Опять же, во время выполнения программы можно использовать технику «обезьяньих патчей» в применении к низкоуровневым системным вызовам. В Python возможно практически всё.

Именно архитектура Python чрезвычайно усложняет оптимизацию.

Для того чтобы проиллюстрировать эту идею, я собираюсь воспользоваться инструментом для трассировки системных вызовов в MacOS, который называется DTrace.

В готовом дистрибутиве CPython нет механизмов поддержки DTrace, поэтому CPython нужно будет перекомпилировать с соответствующими настройками. Тут используется версия 3.6.6. Итак, воспользуемся следующей последовательностью действий:

wget https://github.com/python/cpython/archive/v3.6.6.zip unzip v3.6.6.zip cd v3.6.6 ./configure --with-dtrace make

Теперь, пользуясь python.exe , можно применять DTRace для трассировки кода. Об использовании DTrace с Python можно почитать здесь. А вот тут можно найти скрипты для измерения с помощью DTrace различных показателей работы Python-программ. Среди них — параметры вызова функций, время выполнения программ, время использования процессора, сведения о системных вызовах и так далее. Вот как пользоваться командой dtrace :

sudo dtrace -s toolkit/.d -c ‘../cpython/python.exe script.py’

А вот как средство трассировки py_callflow показывает вызовы функций в приложении.

Трассировка с использованием DTrace

Теперь ответим на вопрос о том, влияет ли динамическая типизация на производительность Python. Вот некоторые соображения по этому поводу:

  • Проверка и конверсия типов — операции тяжёлые. Каждый раз, когда выполняется обращение к переменной, её чтение или запись, производится проверка типа.
  • Язык, обладающей подобной гибкостью, сложно оптимизировать. Причина, по которой другие языки настолько быстрее Python, заключается в том, что они идут на те или иные компромиссы, выбирая между гибкостью и производительностью.
  • Проект Cython объединяет Python и статическую типизацию, что, например, как показано в этом материале, приводит к 84-кратному росту производительности в сравнении с применением обычного Python. Обратите внимание на этот проект, если вам нужна скорость.

Итоги

Причиной невысокой производительности Python является его динамическая природа и универсальность. Его можно использовать как инструмент для решения разнообразнейших задач. Для достижения тех же целей можно попытаться поискать более производительные, лучше оптимизированные инструменты. Возможно, найти их удастся, возможно — нет.

Приложения, написанные на Python, можно оптимизировать, используя возможности по асинхронному выполнению кода, инструменты профилирования, и — правильно подбирая интерпретатор. Так, для оптимизации скорости работы приложений, время запуска которых неважно, а производительность которых может выиграть от использования JIT-компилятора, рассмотрите возможность использования PyPy. Если вам нужна максимальная производительность и вы готовы к ограничениям статической типизации — взгляните на Cython.

Уважаемые читатели! Как вы решаете проблемы невысокой производительности Python?

  • Блог компании RUVDS.com
  • Веб-разработка
  • Python

Да, Python медленный, но меня это не волнует

Jun 2, 2017 08:11 · 2104 words · 10 minute read перевод python

Оригинал: ‘Yes, Python is Slow, and I Don’t Care’ by Nick Humrich; перевод был впервые опубликован на Хабрахабре.

погонщик черепахи

Я беру паузу в моём обсуждении asyncio в Python, чтобы поговорить о скорости Python. Позвольте представиться, я — ярый поклонник Python, и использую его везде, где только удаётся. Одна из причин, почему люди выступают против этого языка, — то, что он медленный. Некоторые отказываются даже попробовать на нём поработать лишь из-за того, что «X быстрее». Вот мои мысли на этот счёт.

Производительность более не важна

Раньше программы выполнялись очень долго. Ресурсы процессора и памяти были дорогими, и время работы было важным показателем. А уж сколько платили за электричество! Однако, те времена давно прошли, потому что главное правило гласит:

Оптимизируйте использование своих самых дорогих ресурсов.

Исторически самым дорогим было процессорное время. Именно к этому подводят при изучению информатики, фокусируясь на эффективности различных алгоритмов. Увы, это уже не так — железо сейчас дёшево как никогда, а вслед за ним и время выполнения становится всё дешевле. Самым дорогим же становится время сотрудника, то есть вас. Гораздо важнее решить задачу, нежели ускорить выполнение программы. Повторюсь для тех, кто просто пролистывает статью:

Гораздо важнее решить задачу, нежели ускорить выполнение программы.

Вы можете возразить: «В моей компании заботятся о скорости. Я создаю веб-приложение, в котором все ответы приходят меньше, чем за x миллисекунд» или «от нас уходили клиенты, потому что считали наше приложение слишком медленным». Я не говорю, что скорость работы не важна совсем, я хочу лишь сказать, что она перестала быть самой важной; это уже не самый дорогой ваш ресурс.

Скорость — единственная вещь, которая имеет значение.

Когда вы употребляете слово «скорость» в контексте программирования, скорей всего вы подразумеваете производительность CPU. Но когда ваш CEO употребляет его применительно к программированию, то он подразумевает скорость бизнеса. Самая главная метрика — время выхода на рынок. В конечном счёте неважно насколько быстро работают ваши продукты, не важно на каком языке программирования они написаны или сколько требуется ресурсов для их работы. Единственная вещь, которая заставит вашу компанию выжить или умереть, — это время выхода на рынок. Я говорю больше не о том, насколько быстро идея начнёт приносить доход, а о том, как быстро вы сможете выпустить первую версию продукта. Единственный способ выжить в бизнесе — развиваться быстрее, чем конкуренты. Неважно, сколько у вас идей, если конкуренты реализуют их быстрее. Вы должны быть первыми на рынке, ну или как минимум не отставать. Чуть затормозитесь — и погибните.

Единственный способ выжить в бизнесе — развиваться быстрее, чем конкуренты.

Поговорим о микросервисах

microservices everywhere

Крупные компании, такие как Amazon, Google или Netflix, понимают важность быстрого развития. Они специально строят свой бизнес так, чтобы быстро вводить инновации. Микросервисы помогают им в этом. Эта статья не имеет никакого отношения к тому, следует ли вам строить на них свою архитектуру, но, по крайней мере, согласитесь с тем, что Amazon и Google должны их использовать. Микросервисы медленны по своей сути. Сама их концепция заключается в том, чтобы использовать сетевые вызовы. Иными словами, вместо вызова функции вы отправляетесь гулять по сети. Что может быть хуже с точки зрения производительности?! Сетевые вызовы очень медленны по сравнению с тактами процессора. Однако, большие компании по-прежнему предпочитают строить архитектуру на основе микросервисов. Я действительно не знаю, что может быть медленнее. Самый большой минус такого подхода — производительность, в то время как плюсом будет время выхода на рынок. Создавая команды вокруг небольших проектов и кодовых баз, компания может проводить итерации и вводить инновации намного быстрее. Мы видим, что даже очень крупные компании заботятся о времени выхода на рынок, а не только стартапы.

Процессор не является вашим узким местом

Если вы пишете сетевое приложение, такое как веб-сервер, скорее всего, процессор не является узким местом вашего приложения. В процессе обработки запроса будет сделано несколько сетевых обращений, например, к базе данных или кешу. Эти сервера могут быть сколь угодно быстрыми, но всё упрётся в скорость передачи данных по сети. Вот действительно классная статья, в которой сравнивается время выполнения каждой операции. В ней автор считает за один такт процессора одну секунду. В таком случае запрос из Калифорнии в Нью-Йорк растянется на 4 года. Вот такая вот сеть медленная. Для примерных расчётов предположим, что передача данных по сети внутри датацентра занимает около 3 мс, что эквивалентно 3 месяцам по нашей шкале отсчёта. Теперь возьмём программу, которая нагружает CPU. Допустим, ей надо 100 000 циклов, чтобы обработать один вызов, это будет эквивалентно 1 дню. Предположим, что мы пишем на языке, который в 5 раз медленнее — это займёт 5 дней. Для тех, кто готов ждать 3 месяца, разница в 4 дня уже не так важна.

В конечном счёте то, что python медленный, уже не имеет значения. Скорость языка (или процессорного времени) почти никогда не является проблемой. Google описал это в своём исследовании. А там, между прочим, говорится о разработке высокопроизводительной системы. В заключении приходят к следующему выводу:

Решение использовать интерпретируемый язык программирования в высокопроизводительных приложениях может быть парадоксальным, но мы столкнулись с тем, что CPU редко когда является сдерживающим фактором; выразительность языка означает, что большинство программ невелики и большую часть времени тратят на ввод-вывод, а не на собственный код. Более того, гибкость интерпретируемой реализации была полезной, как в простоте экспериментов на лингвистическом уровне, так и в предоставлении нам возможности исследовать способы распределения вычислений на многих машинах.

CPU редко когда является сдерживающим фактором.

Что, если мы всё-таки упираемся в CPU?

Что насчёт таких аргументов: «Всё это прекрасно, но что, если CPU становится узким местом и это начинает сказываться на производительности?» или «Язык x менее требователен к железу, нежели y»? Они тоже имеют место быть, однако вы можете масштабировать приложение горизонтально бесконечно. Просто добавьте серверов, и сервис будет работать быстрее 🙂 Без сомнения, Python более требователен к железу, чем тот же C, но стоимость нового сервера гораздо меньше стоимости вашего времени. То есть дешевле будет докупить ресурсов, а не бросаться каждый раз что-то оптимизировать.

Так что, Python быстрый?

На протяжении всей статьи я твердил, что самое важное — время разработчика. Таким образом, остается открытым вопрос: быстрее ли Python языка X во время разработки? Смешно, но я, Google и кое-кто ещё, могут подтвердить насколько продуктивен Python. Он скрывает так много вещей от вас, помогая сосредоточиться на основной вашей задаче и не отвлекаясь на всякие мелочи. Давайте рассмотрим некоторые примеры из жизни.

По большей части все споры вокруг производительности Python скатываются к сравнению динамической и статической типизации. Думаю, все согласятся, что статически типизированные языки менее продуктивны, но на всякий случай вот хорошая статья, объясняющая почему. Что касается непосредственно Python, то есть хороший отчёт об исследовании, в котором рассматривается, сколько времени потребовалось, чтобы написать код для обработки строк на разных языках.

median hours to solve problem

В представленном выше исследовании Python более чем в 2 раза продуктивнее Java. Многие другие также языки программирования дают похожий результат. Rosetta Code провёл довольно обширное исследование различий изучения языков программирования. В статье они сравнивают python с другими интерпретируемыми языками и заявляют:

Python, в целом, наиболее краток, даже в сравнении с функциональными языками (в среднем в 1,2-1,6 раза короче).

По-видимому, в реализации на Python будет как правило меньше строк кода, чем на каком-либо другом языке. Это может показаться ужасной метрикой, однако несколько исследований, в том числе и упомянутых выше, показывают, что время, затраченное на каждую строку кода, примерно одинаково на каждом языке. Таким образом, чем меньше строк кода, тем больше продуктивность. Даже сам codinghorror (программист на C#) написал статью о том, что Python более продуктивен.

Я думаю, будет справедливо сказать, что Python более продуктивен, чем многие другие языки программирования. В основном это связано с тем, что он поставляется «с батарейками», а также имеет множество сторонних библиотек на все случаи жизни. Вот небольшая статья, сравнивающая Python и X. Если вы мне не верите, можете сами убедиться насколько этот язык программирования прост и удобен. Вот ваша первая программа:

import __hello__ 

Но что, если скорость действительно важна?

Мои рассуждения выше могли сложить мнение, что оптимизация и скорость выполнения программы вообще не имеют значения. Во многих случаях это не так. Например, у вас есть веб-приложение и некоторый endpoint, который уж очень тормозит. У вас могут быть даже определённые требования на этот счёт. Наш пример строится на некоторых предположениях:

  1. есть некоторый endpoint, который выполняется медленно
  2. есть некоторые метрики, определяющие насколько медленными может быть обработка запросов

Мы не будем заниматься микро-оптимизацией всего приложения. Всё должно быть «достаточно быстро». Ваши пользователи могут заметить, если обработка запроса занимает секунды, но они никогда не заметят разницу между 35 мс и 25 мс. Вам нужно лишь сделать приложение «достаточно хорошим».

Дисклаймер

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

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

Любые улучшения, сделанные где-либо помимо узкого места, являются иллюзией. — Джин Ким.

Если оптимизация не устраняет узкого места приложения, то вы просто потратите впустую своё время и не решите реальную проблему. Вы не должны продолжать разработку, пока не исправите тормоза. Если вы пытаетесь оптимизировать нечто, не зная конкретно что, то вряд ли результат вас удовлетворит. Это и называется «преждевременной оптимизацией» — улучшение производительности без каких-либо метрик. Д. Кнуту часто приписывают следующую цитату, хотя он утверждает, что она не его:

Преждевременная оптимизация — корень всех зол.

Если быть точным, то более полная цитата:

Мы должны забыть об эффективности, скажем, на 97% времени: преждевременная оптимизация — корень всех зол. Однако мы не должны упускать наши возможности в этих критических 3%.

Другими словами, здесь говорится, что большую часть времени вам не нужно думать об оптимизации. Код и так хорош 🙂 А в случае, когда это не так, нужно переписать не более 3% Вас никто не похвалит, если вы сделаете обработку запроса на несколько наносекунд быстрее. Оптимизируйте то, что поддаётся измерению.

Преждевременная оптимизация, как правило, заключается в вызове более быстрых методов или использовании специфичных структур данных из-за их внутренней реализации. В университете нас учили, что если два алгоритма имеют одну асимптотику Big-O, то они эквивалентны. Даже если один из них в 2 раза медленнее. Компьютеры сейчас настолько быстры, что вычислительную сложность пора измерять на большом количестве данных. То есть, если у вас есть две функции O(log n), но одна в два раза медленнее другой, то это не имеет большого значения. По мере увеличения размера данных они обе начинают показывать примерно одно и то же время выполнения. Вот почему преждевременная оптимизация — это корень всего зла; Это тратит наше время и практически никогда не помогает нашей общей производительности.

В терминах Big-O все языки программирования имеют сложность O(n), где n — кол-во строк кода или инструкций. Не имеет значения, насколько медленным будет язык или его виртуальная машина — все они имеют общую асимптоту. В соответствии с этим рассуждением можно сказать, что «быстрый» язык программирования всего лишь преждевременно оптимизированный, причём непонятно по каким метрикам.

Оптимизируя Python

Что мне нравится в Python, так это то, что он позволяет оптимизировать небольшой участок кода за раз. Допустим, у вас есть метод на Python, который вы считаете своим узким местом. Вы оптимизировали его несколько раз, возможно, следуя советам отсюда и отсюда, и теперь пришли к выводу, что уже сам Python является узким местом. Но он имеет возможность вызова кода на C, а это означает, что вы можете переписать этот метод на C, чтобы уменьшить проблему с производительностью. Вы без проблем можете использовать этот метод вместе с остальным кодом.

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

Существует также язык Cython, который является надмножеством Python. Он представляет из себя смесь с типизированным C. Любой код Python является также валидным кодом и на Cython, который транслируется в C. Вы можете смешивать типы C и утиную типизацию. Используя Cython, вы получаете прирост производительности только в узком месте, не переписывая весь остальной код. Так делает, например, EVE Online. Эта MMoRPG использует только Python и Cython для всего стека, проводя оптимизацию только там, где это требуется. Кроме того, есть и другие способы. Например, PyPy — реализация JIT Python, которая может дать вам значительное ускорение во время выполнения долгоживущих приложений (например, веб-сервера), просто путем замены CPython (реализация по умолчанию) на PyPy.

Итак, основные моменты:

  • оптимизируйте использование самого дорогого ресурса — то есть вас, а не компьютера;
  • выбирайте язык/фреймворк/архитектуру, которая позволяет вам разрабатывать продукты как можно быстрее. Не стоит выбирать язык программирования лишь потому, что программы на нём работают быстро;
  • если у вас проблемы с производительностью — определите, где именно;
  • и, скорее всего, это не ресурсы процессора или Python;
  • если это всё же Python (и вы уже оптимизировали алгоритм), реализуйте проблемное место на Cython/C;
  • и побыстрее возвращайтесь к основной работе.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *