[Перевод] C# vs Rust vs Go: бенчмаркинг производительности в Kubernetes
Простое сравнение производительности трех языков.
Введение
В этой статье обсудим создание высокопроизводительных web-API на Rust, C# и Go и их развертывание в кластере Kubernetes. Также узнаем, как отслеживать использование ресурсов этими API с помощью инструментов мониторинга производительности.
Эта статья расширяет работу, проведенную Anton Putra. Ссылка на его видео. Мы создадим по одному приложению на каждом языке и посмотрим, насколько хорошо они работают.
Для понимания статьи вам понадобятся базовые знания Rust, C#, Go, Docker, Terraform и Kubernetes.
Создание web-API
Мы создадим два веб-API: одно на Rust, другое на C#. Каждый API будет иметь два метода. Первый метод будет использоваться для хранения информации, а второй — для ее получения.
На Rust мы можем использовать Actix. Этот фреймворк предоставляет простой и эффективный способ создания веб-API. Создать методы хранения и извлечения можно с помощью библиотеку Serde для сериализации и десериализации данных.
На C# можно создать веб-API, используя ASP.NET Core. Тоже простой и эффективный фреймворк. Для создания методов хранения и извлечения мы можем использовать Entity Framework Core для взаимодействия с базой данных.
Я клонировал этот репозиторий и отредактировал код, чтобы он соответствовал целям статьи. Ссылку на отредактированный репозиторий можно найти здесь: shyamsundarb-arch/rustvcsvgo (github.com). Посмотрите видео Anton Putra, чтобы понять Helm-чартов и других объектов развертывания. Я внес следующие изменения:
Смена Rocket на Actix в Rust
Создание проекта веб-API на C# с 2 эндпоинтами и необходимыми файлами Docker
Добавление файлов Kubernetes для проекта на C#.
Оценка использования ресурсов с помощью инструментов мониторинга производительности
Чтобы убедиться, что наши API работают эффективно, мы будем использовать инструменты мониторинга производительности для отслеживания использования ресурсов. Эти инструменты предоставят нам информацию о нагрузке на процессор и память API вместе с любыми другими метриками производительности, которые нужно отследить.
Эта информация поможет нам выявить те области API, которые нуждаются в оптимизации и внесении изменений для улучшения производительности. Визуализировать данные будем в Grafana.
Первоначальные наблюдения
Базовые бенчмарки запуска приложения без какой-либо нагрузки приведены ниже.
Нагрузка на ЦП — Без нагрузки
Нагрузка на память — Без нагрузки
Красная линия демонстрирует Rust, синяя представляет Go, а зеленая — C#.
В состоянии покоя нагрузка на процессор у Rust превышает показатели C#, которые в свою очередь немного выше, чем у Go. Нагрузка на память в состоянии покоя больше всего у C#, далее идет Go, затем Rust. Нагрузка на память у Rust минимальна.
Нагрузка для GET-запросов
Мы применим отличный инструмент, который называется Bombardier.
Сначала мы отправим 1 миллион запросов с 50 соединениями всем трем сервисам и ограничим скорость до 100 RPS.
Команда выглядит так:
bombardier -n 1000000 -m GET -c 50 -r 100
Нагрузка на ЦП — Под нагрузкой
C# и Go нагружают процессор практически одинаково, тогда как нагрузка у Rust гораздо меньше, чем в двух предыдущих ЯП.
Нагрузка на память — Под нагрузкой
В данном случае нагрузка на память у C# заметно выше, далее следует Go. Нагрузка на память у Rust вообще не изменилась. Это впечатляет.
Давайте посмотрим на латентности.
p99 — Под нагрузкой
p99 (99% запросов обработано):
C# — 6,10 мс, Go — 4,96 мс, Rust — 4,98 мс.
У Go здесь небольшое преимущество перед Rust, за которым следует C#.
p90 — Под нагрузкой
p90 (90% запросов обработано):
C# — 4,55 мс, Go — 4,51 мс, Rust — 4,54 мс.
Go снова немного опережает Rust. Но C# тоже не сильно отстает.
Статистика Bombardier:
Rust
C#
Go
Самая высокая пропускная способность у C#.
Нагрузка для POST-запросов с ограничениями
Теперь давайте отправим 1 миллион запросов с 50 соединениями всем трем сервисам и ограничим скорость до 100 запросов в секунду. Однако на этот раз мы будем использовать одно соединение, которое делает 1 запрос каждую секунду.
Команда Bombardier:
bombardier -n 1000000 -m POST -c 1 -r 1
Нагрузка на ЦП — Нагрузка POST-запросами
Наивысшая нагрузка на процессор здесь у Golang.
Нагрузка на память — Нагрузка POST-запросами
В данном случае сильнее всех нагружает память C#.
p99 — Нагрузка POST-запросами
Замеры латентности демонстрируют интересные данные. Латентность у Go пытается идти в ногу с C#, но возникают сложности с консистентностью. Rust отлично показывает себя с латентностью вдвое ниже, чем у C#.
p90 — Нагрузка POST-запросами
Опять же, у Rust удивительно низкая латентность.
Статистика Bombardier:
Rust
C#
Go
Заключение
Приведенные в этой статье бенчмарки дают базовое понимание производительности трех языков, но не учитывают сложных сценариев. Если вас интересуют бенчмарки веб-фреймворков, я бы порекомендовал зайти на TechEmpower Framework Benchmarks.
На основе полученных данных можно заключить что Rust демонстрирует стабильную производительность и почти всегда быстрее, чем C# и Go. Однако это ожидаемо, так как Rust работает непосредственно с железом.
В случае с C# и Go производительность неоднородна, поскольку эти два ЯП превосходят друг друга в различных сценариях.
Каждый язык хорош для конкретных сценариев и определенных целей ⬇️
Если вам необходимы знания в Go, предлагаем посмотреть в сторону курса Golang для инженеров. Вы научитесь создавать свой API-сервер, запускать контейнеры, взаимодействовать с Docker и работать с кастомными операторами.
Старт потока — 15 мая. Стать участником: https://slurm.club/3GVCBv8