
Мир веб-разработки предлагает бесконечное количество вариантов HTTP-фреймворков для разных языков программирования. Но как разработчикам понять, какие из них обеспечивают действительно высокую производительность? Под катом команда блога CodeReliant* проводит прямое сравнение некоторых из лучших претендентов на быстродействие. Рассматривает популярные варианты на Javascript/Bun, Java, C#, Go и Rust, проводит бенчмаркинг, оценивает их пропускную способность и время отклика при тестировании.
Выбранные для сравнительного анализа фреймворки имеют репутацию высокопроизводительных, — но посмотрим, как это понятие реализуется в разных стеках.
*Обращаем ваше внимание, что позиция автора может не всегда совпадать с мнением МойОфис.
Вот пять участников тестирования:
Java 21 + vertex 4.4.6
JS/Bun 1.0.6 + elysiajs 0.7
C# 12 + dotnet/ASP.NET 8.0 RC2
Go 1.21.3 + fiber 2.49.2
Rust 1.73.0 + actix-web 4
С помощью стресс-тестирования мы получим достоверные данные об их возможностях. Наше сравнение "лоб в лоб" фокусируется на скорости и масштабируемости с нулевой конфигурацией, чтобы вы могли выбрать правильный фреймворк для своего следующего веб-проекта с высоким трафиком. Если вы хотите ускорить работу API, создать системы с низкой задержкой или выжать максимум из ваших серверов, это сравнение поможет вам выбрать высокопроизводительный HTTP-фреймворк, подходящий для вашего технологического стека.
Окружение и тестовая система
Для тестирования мы запустим минимальную версию HTTP-сервера, возвращающую ответ hello world при запросе /.
Запустим сервер на машине Hetzner:
ОС: Ubuntu 22.04.3 LTS
Ядро: 5.15.0-86-generic
Архитектура: ARM aarch64
Ресурсы: 4 vCPU & 8 GB RAM
Генератор клиентской нагрузки будет находиться на отдельной машине с аналогичными характеристиками, но большими ресурсами — 8 vCPU и 16 GB RAM.
Java и Vertex
Сгенерируем стартовый проект vertex, используя сайт. Также мы будем использовать java 21-oracle, которую только что установили с помощью sdkman.
package io.codereliant.performance; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; public class MainVerticle extends AbstractVerticle { @Override public void start(Promise<Void> startPromise) throws Exception { vertx.createHttpServer().requestHandler(req -> { req.response() .putHeader("content-type", "text/plain") .end("Hello World!"); }).listen(80, http -> { if (http.succeeded()) { startPromise.complete(); System.out.println("HTTP server started on port 80"); } else { startPromise.fail(http.cause()); } }); } }
Просто изменив порт на 80 вместо 8888 и изменив возвращаемую строку на Hello World вместо текста по умолчанию, мы соберем наш сервер с помощью mvn package и запустим его с помощью java -jar target/performance-1.0.0-SNAPSHOT-fat.jar.
java -jar target/performance-1.0.0-SNAPSHOT-fat.jar HTTP server started on port 80 Oct 15, 2023 10:56:15 PM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer INFO: Succeeded in deploying verticle
Bun и Elysia
Elysia упрощает создание проекта: сперва вызываем bun create elysia perf-app, а затем — bun run index.ts.
Для справки мы используем Bun 1.0.6 и Elysia 0.7.0.
import { Elysia } from "elysia"; const app = new Elysia().get("/", () => "Hello World").listen(80); console.log( `? Elysia is running at ${app.server?.hostname}:${app.server?.port}` );

C# и Dotnet
Для C# 12 и dotnet 8.0 RC мы будем использовать ASP.NET Core Minimal APIs.
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
dotnet new web -o perf-app cd perf-app dotnet build -c Release ASPNETCORE_URLS="http://*:80/" ./bin/Release/net8.0/dotnet-app
Golang и Fiber
Fiber — это основанный на Fasthttp веб-фреймворк для Go, который был разработан для обеспечения высокой производительности.
mkdir go-app cd go-app go mod init github.com/codereliant/go-app go get -u github.com/gofiber/fiber/v2 touch main.go
Затем в файле main.go мы используем пример hello world с лендинга https://gofiber.io/.
package main import ( "log" "github.com/gofiber/fiber/v2" ) func main() { app := fiber.New() app.Get("/", func (c *fiber.Ctx) error { return c.SendString("Hello, World!") }) log.Fatal(app.Listen(":80")) }
Для сборки и запуска примера можно использовать эти две строки:
go build main.go ./main
Rust & Actix-web
Информация с главного сайта actix-web:
Actix Web — это мощный, практичный и чрезвычайно быстрый веб-фреймворк для Rust.
Мы можем создать проект с помощью:
cargo new actix-hello cd actix-hello
Затем заменяем содержимое src/main.rs на содержимое ниже, взятое со страницы actix-web getting started:
use actix_web::{web, App, HttpResponse, HttpServer, Responder}; async fn manual_hello() -> impl Responder { HttpResponse::Ok().body("Hey there!") } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/", web::get().to(manual_hello)) }) .bind(("0.0.0.0", 80))? .run() .await }
Наконец, просто собираем бинарник и запускаем его:
cargo build --release # ....... # Finished release [optimized] target(s) in 1m 58s ./target/release/actix-hello
Клиент
В качестве клиента будем использовать oha — инструмент для бенчмаркинга HTTP, написанный на rust и вдохновленный Hey.
Как установить oha, вы можете посмотреть на странице github.
Запустим bombardier с 500 соединениями для 3 миллионов запросов, и повторим этот эксперимент трижды.
oha -c 500 -n 3000000 http://perf-experiment-host/
Ответ должен выглядеть примерно так:

Результаты
Для каждого из вариантов «язык + фреймворк» мы трижды провели бенчмаркинг приложения, а затем отобрали лучшие данные для сравнения.
Пропускная способность

Rust/Actix-web лидирует с самой высокой пропускной способностью, за ним следует Go/fiber.
C#/ASP.NET, несмотря на свою популярность, отстает от лидеров в этом бенчмарке.
Java/Vertex и Bun/Elysia демонстрируют сопоставимые и средние значения пропускной способности.
Задержка

На графике показаны задержки различных HTTP-фреймворков по трем различным параметрам:
99,9% Latency: задержка, при которой обрабатывается 99,9% запросов.
99 % Latency: задержка, при которой обрабатывается 99 % запросов.
Средняя задержка: среднее время ожидания для всех запросов.
Из графика можно сделать несколько выводов:
Rust/Actix-web и Go/fiber демонстрируют не только впечатляющую пропускную способность, но и более низкие задержки по всем трем метрикам.
C#/ASP.NET демонстрирует относительно меньшую среднюю задержку по сравнению с некоторыми другими фреймворками, несмотря на более низкую пропускную способность.
Заметна разница между задержками 99 % и 99,9 %, что подчеркивает вариативность времени обработки запросов в рамках фреймворков.
Выводы
В нашем исследовании Rust/Actix-web стал явным победителем, получив не только самую высокую пропускную способность, но и сверхнизкие задержки по всем метрикам. Следом идет Go/fiber, который впечатляет сочетанием высокой скорости обработки запросов и эффективного времени отклика. Хотя C#/ASP.NET, возможно, и не дотягивает до двух лидеров по пропускной способности, его среднее время задержки было вполне конкурентоспособным, и это говорит о том, что он по-прежнему является оптимальным выбором для многих приложений. С другой стороны, такие фреймворки, как Java/Vertex и Bun/Elysia, показали неплохую производительность, и, возможно, им потребуется дополнительная настройка, чтобы конкурировать с другими фреймворками.
Наверняка показатели выглядели бы иначе, если бы мы потратили некоторое время на настройку языка/фреймворков. Однако мы хотели, чтобы это сравнение было проведено с нулевой конфигурацией.