⚡
GoとRustのざっくり性能比較
はじめに
2026年現在のGoとRustの性能差が気になったので調べました。
今回は、標準出力CLI と JSON APIサーバ という2つのパターンで、GoとRustの性能を比較しています。
ビルドの最適化オプションも含めて、LLMの力を借りて出来る限り実用的な観点で検証しています。
検証環境
| 項目 | バージョン |
|---|---|
| OS | macOS (darwin/arm64) |
| Go | 1.26.0 |
| Rust | 1.93.1 |
リポジトリはこちらで公開しています:
(めっちゃ珍しい言語割合...)

🔧 比較対象のプログラム
標準出力
最もシンプルな標準出力のプログラムにしています。
go-hello/main.go
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
rust-hello/main.rs
fn main() {
println!("Hello World!");
}
JSON API サーバ
GET /hello で {"message": "Hello World!"} を返すAPIサーバです。
Go は標準ライブラリのみ、Rust は axum を使用しています。
go-api/main.go
package main
import (
"encoding/json"
"net/http"
)
func main() {
http.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"message": "Hello World!"})
})
http.ListenAndServe(":8080", nil)
}
rust-api/src/main.rs
use axum::{routing::get, Json, Router};
#[tokio::main]
async fn main() {
let app = Router::new().route("/hello", get(|| async {
Json(serde_json::json!({"message": "Hello World!"}))
}));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
ビルドの最適化
単純に go build や cargo build --release するだけでなく、バイナリサイズや実行速度を最適化するオプションを適用しました。
この部分はGoやRustの仕様に精通しているであろうLLMの調査結果をそのまま適用させています...!
Go のビルドオプション
CGO_ENABLED=0 go build -ldflags='-s -w' -trimpath -o app .
| オプション | 効果 |
|---|---|
CGO_ENABLED=0 |
CGO_ENABLED=0 → cgo を無効化し、Go 純正の実装を使用 (Linux では静的リンク、macOS では libSystem への動的リンクは残る) |
-ldflags='-s' |
シンボルテーブルを削除 |
-ldflags='-w' |
DWARF デバッグ情報を削除 |
-trimpath |
バイナリからビルドパス情報を除去 |
Rust のビルドオプション
RUSTFLAGS="-C target-cpu=native" cargo build --release
Cargo.toml の [profile.release] セクションで以下を設定しています:
| オプション | 値 | 効果 |
|---|---|---|
opt-level |
3 |
最大限の最適化(速度優先) |
lto |
"fat" |
クレート間のリンク時最適化(バイナリサイズ削減+速度向上) |
codegen-units |
1 |
コード生成を単一ユニットで実行(最適化の質向上) |
panic |
"abort" |
パニック時にアンワインドせず即座に中断(バイナリサイズ削減) |
strip |
"symbols" |
シンボル情報を削除(バイナリサイズ削減) |
RUSTFLAGS="-C target-cpu=native" は、ビルドマシンのCPUに最適化された命令セットを使用するオプションです。
測定結果
標準出力
| 項目 | Go | Rust | Winner |
|---|---|---|---|
| バイナリサイズ | 1.64 MB | 0.30 MB | 🦀 Rust (5.5倍) 🦀 |
| 平均レイテンシ (1000回) | 2.587ms | 2.050ms | 🦀 Rust (1.3倍) 🦀 |
| ピークメモリ (RSS) | 3,902 KB | 1,296 KB | 🦀 Rust (3.0倍) 🦀 |
| コンパイル時間 | 0.482s | 3.190s | 🐹 Go (6.6倍) 🐹 |
API サーバ
| 項目 | Go | Rust | Winner |
|---|---|---|---|
| バイナリサイズ | 5.58 MB | 0.70 MB | 🦀 Rust (8.0倍) 🦀 |
| Requests/sec | 34,363 | 37,199 | 🦀 Rust (1.1倍) 🦀 |
| 平均レイテンシ | 0.291ms | 0.269ms | 🦀 Rust (1.1倍) 🦀 |
| メモリ使用量 (RSS) | 17.2 MB | 2.4 MB | 🦀 Rust (7.2倍) 🦀 |
| コンパイル時間 | 0.608s | 18.583s | 🐹 Go (30.6倍) 🐹 |
計測条件
- 標準出力: シェルスクリプトで1000回ループし、平均を算出
- API サーバ:
ab -n 10000 -c 10 http://localhost:8080/helloで計測 - メモリ (標準出力):
/usr/bin/time -lのピーク RSS - メモリ (API サーバ): ウォームアップ後に
psコマンドで RSS を取得
まとめ
今回の測定結果をより以下のような傾向が見えてきました。
- バイナリサイズ: Rust が圧倒的に小さい(5〜8倍)
- 実行速度/スループット: Rust がやや速い(1.1〜1.3倍)
- メモリ効率: Rust が圧倒的に少ない(3〜7倍)
- コンパイル時間: Go が圧倒的に速い(6〜30倍)
ランタイム性能ではRustが全体的に優位ですが、特にメモリ効率とバイナリサイズの差が顕著でした。
一方で、コンパイル時間はGoが圧倒的に速く、開発体験の面ではGoに大きなアドバンテージがあります。
用途に応じて適切な言語を選択していきたいですね。
ソースコードは以下のリポジトリで公開しています。ぜひ手元で試してみてください!
Discussion
静的リンクしてるGoとglibcを動的リンクしてるRustと比べているのでは条件が違うのでは?
Goの CGO_ENABLED=0 はMacOSでは完全に静的リンクできていなかったです、オプションの効果部分を修正しました。 なので、バイナリサイズが大小比較の説明部分は元の記述で間違いではないと考えています。
記事にあるリポジトリを手元のx86-64版Ubuntuにクローンして記事に説明されてる手順でそれぞれビルドし、ldd コマンドで確認したところ Goのappは
ダイナミックリンクされない実行ファイルであることが確認されたのですが、rust-apiでは
実行時にリンクされる動的ライブラリファイルが複数出力されました。
ponyo877さんの環境で同じ結果になるかはわかりませんが
ということもあるので確認されると安心ですね。
macOSであれば
otool -L target/release/rust-api等で確認できるのではないかと思います。ありがとうございます!
今回の計測は macOS (darwin/arm64) で行っているのですが、macOS 上で otool -Lで動的ライブラリ依存を確認したところ以下の結果でした:
前述の通りGo は 1.12 以降、macOS では syscall を libSystem 経由で行う仕様になっています。
そのため macOS では Go もRust も libSystem.B.dylib に動的リンクしており、リンク方式の条件はmacOSではたまたま同等っぽい感じになっていました。
記事の通りオプションの部分はLLMに投げっぱなしだったのでその点について詳しくご検証・ご指摘いただき助かりました!
ありがとうございました!
ちょっときついことを言うようで申し訳ありませんが・・・
このベンチマークはコードが単純すぎて、実質的に言語ではなくライブラリの性能比較になっていますね。
実行速度の差はほとんどなく、UX 的には大きな違いは出ません。
そもそも、このレベルのコードはユーザにとって意味のある比較になっていません。
ベンチマークもテストの一種なので、どの部分を測定するのかを明確にしないと、曖昧なまま結論を導くことになります。
時間を外部から測定する場合、比較しているのは実行環境であり、言語そのものの影響は非常に小さくなります。
もしどうしても単純コードで速度を測るなら、処理の開始から終了までを精細な時刻計測関数で測る必要があります。
また、最適化の判定が目的でないなら、定数は最適化されやすいためできるだけ多くの変数を使う方が適切です。
条件分岐も、単純な if だけでなく、多分岐を含めた方がより実態に近い計測になります。
現状のような計測方法では、得られる印象がどうしても偏りやすくなります。
また、こうした内容に安易な“いいね”が集まるのは、結果的に印象操作に加担してしまうため、技術的な議論としてはあまり健全ではないと感じます。
コメントありがとうございます。ご指摘いただいた点、ほぼご指摘のとおりです!
今回のコードは非常にシンプルなため、言語そのものの計算性能というよりは、ランタイムの起動コストやライブラリの実装差を測っている側面が大きいです、おっしゃる通りです...
特に標準出力のベンチマークではシェルからの外部計測のためプロセス起動のオーバーヘッドが支配的になっており、言語固有の実行速度を正確に反映できていない
内部での精密な時刻計測や、定数の最適化回避、多分岐を含むより実践的なワークロードでの比較があれば、より説得力のある結果になったと思います
一方で、バイナリサイズ・メモリ使用量・コンパイル時間といった項目については、コードの複雑さに依存しにくい指標であり、ある程度は実用上の参考にはなると考えています。
記事タイトルや結論部分で「言語の性能比較」と受け取れる表現になっている点は、誤解を与えないような形に修正しようと思います。
多くのいいねは記事の不完全さは筆者である私の責任であり、読者の方々が参考になったと感じてくださったこと自体は素直にありがたく思っています!
貴重なフィードバックをありがとうございました!!
計測条件の章に「計測上の注意点」のmessageを追加しました