Язык Python – это гарантированно высокая производительность. CPython 3.6, NumPy и улучшенная совместимость, PyPy с динамической компиляцией etc.
Многие компании бросают язык Python и переходят на другие в поисках повышения производительности. Но в этом нет необходимости, и сейчас я объясню вам, почему.
Включение Japronto
Japronto представляет собой микрофреймворк, который предназначен для микросервисов. К его основным характеристикам относятся скорость, легкость и масштабируемость. За счет модуля asyncio можно задействовать синхронный и асинхронный режимы программирования. И это бесстыдно быстро! Даже быстрее, чем NodeJS или Go.
Один из пользователей отметил, что HTTP-сервер, грамотно оптимизированный на Go с stdlib, может выдать показатель, который на 12% быстрее представленного на графике. Также существует сервер fasthttp (Go), который на 18% медленнее Japronto. Более подробная информация: https://github.com/squeaky-pl/japronto/pull/12 и https://github.com/squeaky-pl/japronto/pull/14.
На графике показано, что WSGI-сервер Meinheld почти на одном уровне с NodeJS и Go. По сравнению с предыдущими 4 асинхронными решениями Python, это отличный инструмент. Поэтому никогда не доверяйте заявлениям о том, что асинхронные системы всегда быстрее, так как на производительность влияет немало факторов.
Несложная программа «Hello world!» наглядно продемонстрировала потерю производительности в использовании некоторых приложений/фреймворков.
Приведенные результаты получены с AWS c4.2xlarge (8 VCPU), запущенном в Сан-Паулу. ОС – Ubuntu 16.04.1 LTS (релиз под названием «Xenial Xerus»), хранилище magnetic, HVM-виртуализация, а также процессор, определяемый ОС как Xeon® CPU E5-2666 v3 @ 2.90GHz CPU. Чтобы проверить язык Python и его текущую производительность, использовался Python 3.6.
Проводимый процесс – строго однопользовательский. Для тестирования задействовали wrk с 1 потоком, 100 соединениями и 24 конвейерными (одновременными) запросами на подключение (всего 2,4 тыс. параллельных запросов).
Конвейерная обработка HTTP играет в этом решающую роль, поскольку это одна из оптимизаций, которые Japronto учитывает при выполнении запросов.
Большинство серверов выполняют запросы от конвейерных клиентов так же, как и в обычном порядке. Конвейерная обработка – это метод, при котором клиенту не нужно ждать ответа перед отправкой последующих запросов по одному и тому же TCP-соединению. Чтобы обеспечить целостность связи, сервер отправляет несколько ответов в таком порядке, в котором принял запросы.
Язык Python и оптимизация
Когда много небольших запросов GET отправляются клиентом в виде pipeline-цепочки, существует высокая вероятность того, что они придут на сервер в 1-ом пакете TCP (алгоритм Нейгла) и там прочитаются посредством одного системного вызова.
Системный вызов и перевод данных из ядра в пространство пользователя – дорогостоящая операция, если сравнить ее, например, с перемещением памяти внутри одного процесса. Поэтому очень важно выполнять лишь необходимые системные вызовы.
Когда Japronto получает и успешно разбирает данные, он пытается выполнить все запросы как можно быстрее, распределить ответы в правильном порядке, а затем записать в один системный вызов. Также можно бы было применить scatter/gather IO (системные вызовы), однако Japronto их еще не использует.
Обратите внимание, что описанное не всегда возможно, так как некоторые из запросов могут занимать слишком много времени, и ожидание неизбежно увеличит сетевую задержку. Будьте внимательны при настройке эвристики и учитывайте стоимость системных вызовов, а также ожидаемое время для обработки запросов.
Кроме задержки записи для конвейерных клиентов, существуют и другие методы. Japronto почти полностью реализован на языке C. Парсер, протокол, соединитель, маршрутизатор, запрос и объекты ответа выполнены как расширения на C.
Другие специфические моменты
Japronto пытается отложить создание Python-аналогов своих структур: словарь заголовков не будет создан, пока не реализуют его запрос в представлении. Границы токенов отмечаются заблаговременно, однако нормализация ключей заголовка и создание str-объектов выполняются лишь в момент обращения к ним.
Микрофреймворк Japronto полагается на С-библиотеку picohttpparser. Она напрямую использует инструкции по обработке текста, найденные в современных процессорах с расширениями SSE4.2 (представлены почти у любого 10-летнего процессора x86_64). Это нужно для быстрого сопоставления границ HTTP-токенов. Ввод-вывод обрабатывается uvloop. На самом низком уровне выполняется системный epoll-вызов, предоставляющий асинхронные уведомления о готовности записи и чтения.
Но язык Python – это также и сборник мусора, поэтому нужно проявлять осторожность при разработке высокопроизводительных систем, чтобы не увеличивать нагрузку без причин. Japronto пытается избегать ссылочных циклов и совершать как можно меньше allocation- и deallocation-операций. Это возможно благодаря предварительному распределению объектов на арены (arenas). Микрофреймворк также старается повторно использовать объекты, а не избавляется от них, создавая новые.
Все распределения выполняются как кратные 4 Кб. Внутренние структуры такие, что часто используемые данные располагаются рядом: это минимизирует вероятность промахов кэша. Japronto избегает ненужного копирования между буферами и совершает немало операций на месте.
Помогите, разработчики Open Source!
Я работаю над Japronto уже 3 месяца подряд: это как будние дни, так и выходные. Думаю, пришло время поделиться результатами своих трудов с обществом. На сегодняшний день микрофреймворк Japronto обладает внушительным набором функций:
- Реализация HTTP 1.x с поддержкой пакетных загрузок
- Полная поддержка HTTP-конвейерной обработки
- Keep-alive-соединения со сборщиком неактивных соединений, который настраивается
- Поддержка синхронных и асинхронных представлений
- Простая маршрутизация
- Модель Master-multiworker, основанная на forking
- Поддержка перезагрузки кода при изменениях
В дальнейшем планирую рассмотреть веб-сокеты и потоковые HTTP-ответы в асинхронном режиме. Впереди немало работы с документированием и тестированием. Желающих помочь прошу связаться со мной посредством Twitter. Репозиторий Japronto на GitHub. Кроме того, я открыт для предложений в области разработки на Python.
Подытожим
Язык Python. Что ж, не все из приведенных выше методов являются исключительно его спецификациями. Их можно было бы использовать и в других языках, таких как Ruby, JavaScript или даже PHP. Подобный вид разработки мне тоже интересен, но это невозможно, если нет соответствующего финансирования.
С любовью к языку программирования Python.
Комментарии