👌
Bun, Deno, Go, Node, Rust, Zig: A Benchmark
Bun, Deno, Go, Node, Rust, Zig: A Benchmark
vim-jp slack の #lang-go で、Go vs Node が土日に繰り広げられていました(月曜日気づいた)。
mattn さんが、Go と Node の速度を比較するベンチマークを書いていたので、それを bun, deno, go, node, rust, zig で書いてみました。
(zig わからないので未完成です 🙇)
ベンチマーク
Language | Requests per second | Time per request |
---|---|---|
bun | 11793.40 [#/sec] (mean) | 0.848 [ms] (mean) |
deno | 32913.58 [#/sec] (mean) | 0.304 [ms] (mean) |
go | 85736.82 [#/sec] (mean) | 0.117 [ms] (mean) |
node | 11187.35 [#/sec] (mean) | 0.894 [ms] (mean) |
rust | 20267.08 [#/sec] (mean) | 0.493 [ms] (mean) |
zig | 未測定 | 未測定 |
ということで、Go が一番速いです。
Go >> Deno > Rust > Bun > Node という結果になりました。
Bombardier (追記)
Zenn にも投稿したら、別のベンチマーカーを教えていただいたので試しました
Language | Reqs/sec Avg | Latency Avg |
---|---|---|
bun | 87817.82 | 111.74us |
deno | 39247.26 | 252.97us |
go | 59071.51 | 166.08us |
node | 17808.81 | 567.93us |
rust | 36398.10 | 271.31us |
rust (release) | 36390.67 | 271.66us |
rust (multi-thread) | 36014.16 | 274.18us |
hyper | 75011.92 | 131.11us |
zig | 未測定 | 未測定 |
このベンチマークだと、
Bun > hyper > Go > Deno > Rust > Node になりました
注意
この計測は、特定の言語やフレームワークを批判するものではないです。
それぞれの言語やフレームワークには、それぞれの良いところ、悪いところがあると思っています。
また、計測方法や各言語の最適化ができていないと思います。
間違いがあったときは申し訳ございません。
ベンチマークの実行
install
sudo apt install apache2-utils
run
ab -k -c 10 -n 10000 http://127.0.0.1:3000/
ベンチマークのコード
Go
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html")
fmt.Fprintf(w, "<h1>Hello World</h1>")
})
http.ListenAndServe(":3000", nil)
}
Rust
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
fn main() {
let listener = TcpListener::bind("127.0.0.1:3000").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 512];
stream.read(&mut buffer).unwrap();
let response = "HTTP/1.1 200 OK\r\n\r\n<h1>Hello World</h1>";
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
Zig
const std = @import("std");
const net = std.net;
const StreamServer = net.StreamServer;
const Address = net.Address;
pub const io_mode = .evented;
pub fn main() anyerror!void {
var stream_server = StreamServer.init(.{});
defer stream_server.close();
const address = try Address.resolveIp("127.0.0.1", 3000);
try stream_server.listen(address);
while (true) {
const client = try stream_server.accept();
const response = "HTTP/1.1 200 OK\r\n\r\n<h1>Hello World</h1>";
try client.write(response);
}
}
Node
const http = require("http");
const port = 3000;
const server = http.createServer((request, response) => {
response.writeHead(200, {
"Content-Type": "text/html",
});
const responseMessage = "<h1>Hello World</h1>";
response.end(responseMessage);
});
server.listen(port);
Deno
const server = Deno.listen({ port: 3000 });
for await (const conn of server) {
serveHttp(conn);
}
async function serveHttp(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
const body = "<h1>Hello World</h1>";
requestEvent.respondWith(
new Response(body, {
status: 200,
})
);
}
}
Bun
// TypeScript: http.ts
export default {
port: 3000,
fetch(request: Request) {
return new Response("Hello World");
},
};
Makefile
.phony:
build-go:
go build go/main.go && ./main
build-rust:
rustc rust/main.rs && ./main
build-zig:
zig build-exe zig/main.zig && ./main
run-go:
go run go/main.go
run-node:
node node/main.js
run-bun:
bun run bun/main.ts
run-deno:
deno run --allow-net deno/main.ts
bench:
ab -k -c 10 -n 10000 http://127.0.0.1:3000/
bench-go:
ab -k -c 10 -n 10000 http://127.0.0.1:3000/ > bench/go.txt
bench-rust:
ab -k -c 10 -n 10000 http://127.0.0.1:3000/ > bench/rust.txt
bench-node:
ab -k -c 10 -n 10000 http://127.0.0.1:3000/ > bench/node.txt
bench-bun:
ab -k -c 10 -n 10000 http://127.0.0.1:3000/ > bench/bun.txt
bench-deno:
ab -k -c 10 -n 10000 http://127.0.0.1:3000/ > bench/deno.txt
bench-zig:
ab -k -c 10 -n 10000 http://127.0.0.1:3000/ > bench/zig.txt
check-port:
echo 'sudo lsof -i :3000'
Discussion
計測ありがとうございます。
この記事は今後長く参照されることになると思いますので、各処理系のバージョンを明記しておいて他の方が同じ条件で追試を行うことができるようにするとよいと思います。
また、それぞれについてのコンパイルで最適化のオプションがついていないようなので、それによる違いも調べてみたらどうでしょう。 特にrustはデバッグビルドとリリースビルドで速度にかなりの差があるというのを聴いたことがあります。
ありがとうございます。
後ほど追記します🙇
Rust の場合ですが、そもそも TCPListener でのシングルスレッド処理なので遅いのは仕方ないのかなという気持ちです。非同期処理したいなら、パッケージ入れるしかないかなと...
rustc でコンパイルしているので、後で最適化したものも追記します。
hyper + tokio を追加しました
こんにちわ。Node/Deno/Bunのベンチマークをよくとってる者です。abは遅いので、他のベンチマークツール使ったほうがいいと思います。例えば、Bunはこの辺、かなりシビアにベンチマークとってて、Bombardierというツールを推奨しています。
コマンドは
とかやります。
あと、DenoはDeno flashというdeprecatedになりそうですが、
Deno.serve
コマンドで起動するサーバーを使った方が速かったり、DenoもBunもContent-Typeが書かれてなかったりするので、直しておくといいかもしれません!同じような趣旨でDenoのフレームワーク等と比べてるレポジトリがあるので貼っておきます。貴重な情報ありがとうございます🙇
ab 遅いのですか...
ベンチマークはどれを使うべきなのか、かなり困っていました..
助かります
土日に試してみます!
bun が異様に速いですね...
そうっすねー。Bun速いっす。Denoも、
Deno.serve
使うとBunほどじゃないけど速いです。JSランタイムだと現状はBun、Deno、Node.jsの順になりますね。
html 配布とかでもう一回ベンチしてみようかなーと思いました
もしご存じだったらなのですが、ベンチまとめたリポジトリとかって知ってます?
現状、shellの自作のしかないので...
HTTPサーバーだったら上記のDenoやつとBunのフレームワークをベンチしてるこちらのレポジトリが参考になります。
JS/TSになっちゃいますが、ベンチマークスクリプトがよくて自動でスコアが書かれたMarkdown作ってくれたりします。あとはGoなら似たようフレームワーク比較するのがありそうっすね。