Перейти к содержанию

Java vs Rust Hyper - сравнение производительности HTTP-сервера Hello world

Posted on:8 апреля 2023 г. at 10:08

Example Dynamic OG Image link В этой статье я собираюсь сравнить собственный HTTP-сервер Java с Hyper HTTP-сервером Rust. Основываясь на своих исследованиях, я обнаружил, что Hyper - самый популярный сервер на стороне Rust. Если есть лучшие и популярные альтернативы, пожалуйста, дайте мне знать.

Rust - это скомпилированный язык, который создает машинный код, в то время как Java также является скомпилированным языком, который создает байт-код, который выполняется внутри JVM. Сравнение, возможно, не настолько справедливо. В любом случае, давайте проведем тесты и проверим результаты.

Тестовая настройка

Тесты выполняются на MacBook Pro M1 с 16 ГБ оперативной памяти.

Версии программного обеспечения следующие:

Код HTTP-сервера hello world в обоих случаях выглядит следующим образом:

Java

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class NativeHelloWorldServer {

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(3000), 0);
        server.createContext("/", new MyHandler());
        server.setExecutor(Executors.newCachedThreadPool());
        server.start();
    }

    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "Hello world!";
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

}

Rust

use std::convert::Infallible;
use std::net::SocketAddr;

use bytes::Bytes;
use http_body_util::Full;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use tokio::net::TcpListener;

async fn hello(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
  let response = Response::builder()
    .header("Content-type", "text/plain")
    .body(Full::new(Bytes::from("Hello World!")))
    .unwrap();
  Ok(response)
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
  let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
  let listener = TcpListener::bind(addr).await?;
  loop {
    let (stream, _) = listener.accept().await?;
    tokio::task::spawn(async move {
      if let Err(err) = http1::Builder::new()
        .serve_connection(stream, service_fn(hello)).await {
          println!("Error serving connection: {:?}", err);
        }
    });
  }
}

Cargo.toml

[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

[dependencies]
bytes = "1"
hyper = { version = "1.0.0-rc.3", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1.0-rc.2"

Код Rust был собран в режиме релиза:

> cargo build --release
...
Compiling hyper v1.0.0-rc.3
Compiling hello_world v0.1.0 (/Users/mayankc/Work/source/perfComparisons/rust)
Finished release [optimized] target(s) in 19.88s

Тестирование

Каждый тест выполняется для 5 миллионов запросов.

Тесты выполняются для 10, 100 и 300 одновременных подключений.

Нагрузочный тест выполняется с помощью инструмента HTTP-тестирования Bombardier.

Ниже приведены таблицы, показывающие результаты для каждого уровня параллелизма:

Анализ

Rust полностью превосходит Java по всем аспектам, будь то RPS, задержки, процессор или память.

ПОБЕДИТЕЛЬ: Rust Hyper