Сравнение производительности Golang vs. Python

Перевод статьи Vadym Zakovinko “Golang vs. Python: Comparing Performance and Benchmarks”.

Здравствуйте, меня зовут Вадим, и это моя история о том, как я начал изучать Go, каково это по сравнению с Python (языком, который я сейчас использую на работе), и о бенчмаркинге.

Скачивайте книги ТОЛЬКО на русском языке у нас в телеграм канале: PythonBooksRU

Я считаю, что каждый разработчик должен постоянно учиться, чтобы быть хорошим специалистом в своем деле. И дело не только в знании новых фреймворков, баз данных или платформ, таких как AWS Lambda. Речь идет о знании того, как сервисы, которые вы используете изо дня в день, взаимодействуют друг с другом, о понимании того, когда ваш любимый язык использует ссылку на объект или копию объекта, и о многом другом.

Я использую Python в качестве основного языка для написания производственного кода уже 10 лет. Но даже будучи преданным разработчиком Python, я пробовал разные языки. Мне нравится изучать новые языки, потому что я могу из этого почерпнуть что-то для своей повседневной работы – даже из тех языков, которые я не буду использовать или из языков, которые мне не очень нравятся, например, Ruby или Java. Кроме того, для меня не очень важно, новый ли это язык. Мне нравятся и старые, редко используемые языки. Например, я пробовал Lisp для чего-то большего, чем написание конфигурации для Emacs. Мне также очень нравится Erlang, но он не слишком популярен в наши дни.

Знакомство с Go

Несколько недель назад я начал искать новый язык для изучения. Прежде всего, я начал изучать Elixir, поскольку мне нравится Erlang, но после прочтения учебника он мне не очень приглянулся. Через несколько дней я остановился на Go, так как много слышал об этом языке из разных источников. На первый взгляд, язык не казался сложным, но он требовал иного подхода к архитектуре приложений, чем, например, Python. Поэтому я решил изучить его поглубже.

Шаг первый

Сначала я погрузился в сообщество Go. В сообществе Go есть замечательный учебник, из которого вы почерпнете многие сведения о языке. Я считаю, что это отличная отправная точка: www.tour.golang.org. Учебник состоит из трех разделов, плюс раздел приветствия:

  • “Basic” показывает основы синтаксиса Go, работу с переменными и функциями, операторы управления потоком и более сложные типы.
  • Раздел “Methods and Interfaces” объясняет, как создавать и реализовывать интерфейсы.
  • “Concurrency in Go” объясняет все основы параллелизма.

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

Шаг второй

Следующим шагом в изучении Go стал просмотр презентаций. Лучшая из них (на мой взгляд) – Go Concurrency Patterns Роба Пайка. Эту презентацию легко смотреть и она очень мотивирует. Я также рекомендую посмотреть на YouTube предложения, основанные на этом видео.

Шаг третий

Прежде чем попробовать создать свое собственное простое приложение (и начать сравнение приложений на Go с приложениями на Python), я изучил еще один замечательный учебник – Learn Go with tests. В этом учебнике рассказывается о Go и о том, как писать код, используя методологию разработки на основе тестов.

Просмотрев все эти учебники и видео, я решил наконец попробовать самостоятельно создать простое приложение на Go. Я решил написать сервис для сокращения URL-адресов, похожий на bit.ly или goo.gl, но более простой, без статистики и пользовательского интерфейса.

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

Далее я начал писать код для приложения (сначала – код генерации коротких строк с тестами). Когда я начал думать о хранении, я застрял. Это произошло потому, что я не понимал, как правильно разделить мой код. Для меня ответ был найден в схеме проекта стандартной библиотеки Go и в обзоре некоторых популярных библиотек и приложений на Go. Кроме того, я нашел модули Go, которые не были освещены ни в одном из учебников.

.
├── api
│  └── openapi.yaml
├── build
│  ├── ci
│  │ └── .travis.yml
│  ├── Dockerfile
│  └── docker-compose.yml
├── internal
│  ├── generator
│  │ ├── generator.go
│  │ └── generator_test.go
│  └── storage
│    ├── redis_storage.go
│    ├── redis_storage_test.go
│    ├── simple_storage.go
│    ├── simple_storage_test.go
│    └── storage.go
├── third_party
│  ├── locustfile.py
│  ├── redoc-static.html
│  └── wrk_post.lua
├── .gitignore
├── .travis.yml -> build/ci/.travis.yml
├── LICENSE
├── README.md
├── go.mod
├── go.sum
└── main.go

Свою вторую попытку я начал с описания схемы OpenAPI для моего API. Затем я скопировал генерирующую функцию из предыдущего кода и начал работу над хранилищем URL.

Изучая макеты проектов для приложений на Go, я нашел Bolt – простую библиотеку хранения ключевых значений, написанную на Go. В описании этой библиотеки говорится, что хранилище можно использовать для хранения большого количества данных. Я решил попробовать, поскольку используемый мною подход позволил бы легко заменить эту библиотеку на другое хранилище данных без изменения большого количества кода.

Бенчмаркинг и производительность Go vs. Python

Мое первое приложение на Go готово, и чтобы сравнить Golang с Python, я хочу провести сравнительный тест. Обычно я использую для этого wrk, но в этот раз я решил использовать Locust. С Locust проще создать тест, который будет одновременно использовать все эндпоинты, которые у меня есть.

Я был удивлен, когда результат составил около 43 запросов в секунду, что является довольно маленьким числом. Поначалу я предположил, что число так мало, потому что хранилище использует только файлы на моем диске. Я решил написать такое же приложение на Python, используя asyncio, aiohttp и aioredis в качестве хранилища, проверить результаты, а затем реализовать хранилище, использующее Redis, в приложении на Go.

И это сработало! Я получил тот же результат, что и в эталонном тесте для обоих приложений на Python и Go. Прежде всего, я запустил wrk для проверки конечной точки API для получения длинного URL по короткой строке. Это было правильное решение, потому что я получил разные результаты. Приложение Go было в 9 раз быстрее, а Python выполнял более 43 запросов за секунду. Теперь цифры составляли около 280 запросов в секунду для Python и 2500 для Go. Что, с моей точки зрения, ближе к истине.

Locust, вероятно, предлагает принцип master/slave для проведения надлежащего тестирования вместо использования fork/threads, и именно поэтому 43 запроса в секунду – это предел для Locust в текущей конфигурации.

*Распечатка результатов финальных тестов

Следующий тест был проведен для API хранения длинных URL. Он показал, что мое приложение Go может обрабатывать около 20 запросов в секунду. Это подтвердило мои ожидания. После этого теста я сделал реализацию хранения данных, использующую Redis, и снова запустил все тесты с помощью wrk.

Ниже вы можете увидеть график с результатами тестов и сравнить скорость Go и Python. Я использовал Raspberry Pi 3 B+ в качестве платформы для приложения и ноутбук с i7 и 16 Гб оперативной памяти для запуска wrk (у Locust была такая же конфигурация). Я специально использовал Raspberry Pi, чтобы дать ноутбуку, на котором я проводил тест, больше мощности.

Все эти результаты получены при работе с 12 потоками и 400 соединениями в течение 30 секунд. Цифры – это общее количество для всех потоков, а не только для одного. Если вас интересуют необработанные результаты wrk, перейдите по этой ссылке.

Мои личные результаты

Честно говоря, приложение, которое я написал на Python и запускал с помощью Gunicorn с самого начала, имело худшие результаты, чем я вам показал. Оно выдавало примерно 360 запросов в секунду. Но как только я увеличил количество воркеров Gunicorn с 2 (по умолчанию) до 4, результаты удвоились. На Raspberry Pi 4 CPU, и если воркеров больше, чем CPU, это может привести к ухудшению результатов.

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

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

Кроме того, я считаю, что для написания приложений на Go разработчики должны больше думать о дизайне приложений, чем при использовании других языков, таких как Python. Иначе в будущем они не смогут вносить изменения и расширять функциональность приложения. Или это будет крайне сложно. Кстати, вот репозиторий с моим Go-приложением, которое я надеюсь доработать и расширить в будущем: https://github.com/Quard/gosh.

И последнее. Был ли у кого-то из вас похожий опыт изучения одного из этих языков? Каковы были ваши выводы?