Rust vs Go ベンチマーク完全版レポート(コード全量・詳細データ・倍率付)
このレポートは、RustとGoのパフォーマンスを5つの観点で比較し、全ソースコードと詳細な計測データ、およびRustの優位性を数値化したものです。
1. 総合結果サマリー
| カテゴリ | 実験内容 | Rust (release) | Go | Rust vs Go (倍率) |
|---|---|---|---|---|
| CPU計算 | 実験1: フィボナッチ(n=40) | 0.228s | 0.391s | 1.71倍 高速 |
| メモリ | 実験2: 1000万構造体生成 | 0.038s | 0.153s | 4.02倍 高速 |
| 並列処理 | 実験3: 100万素数の合計 | 0.053s | 0.065s | 1.22倍 高速 |
| 文字列 | 実験4: 100万回文字列結合 | 0.004s | 0.005s | 1.25倍 高速 |
| データ処理 | 実験5: 10万件JSON変換 | 0.034s | 0.115s | 3.38倍 高速 |
2. 各実験の詳細と全ソースコード
実験1: CPU計算(再帰フィボナッチ)
純粋な計算アルゴリズムの実行速度を比較。
速度比較
| 言語 | 実行時間 (real) | Rust vs Go (倍率) | 備考 |
|---|---|---|---|
| Rust (release) | 0.228s | 1.71倍 高速 | LLVMによる強力な最適化 |
| Go | 0.391s | (基準) | 標準的な高速性 |
Rustコード (rust-bench/src/main.rs)
fn fib(n: u32) -> u32 {
if n <= 1 { return n; }
fib(n - 1) + fib(n - 2)
}
fn main() {
let n = 40;
println!("Fibonacci({}) = {}", n, fib(n));
}
Goコード (go-bench/main.go)
package main
import "fmt"
func fib(n uint32) uint32 {
if n <= 1 { return n }
return fib(n-1) + fib(n-2)
}
func main() {
n := uint32(40)
fmt.Printf("Fibonacci(%d) = %d\n", n, fib(n))
}
実験2: メモリ割り当て(GC負荷)
大量の構造体を生成し、メモリ管理コストを比較。
速度比較
| 言語 | 実行時間 (real) | Rust vs Go (倍率) | 備考 |
|---|---|---|---|
| Rust (release) | 0.038s | 4.02倍 高速 | スタック/連続メモリ活用 |
| Go | 0.153s | (基準) | ヒープ割り当て & GC負荷 |
Rustコード (rust-mem/src/main.rs)
struct Point { x: i32, y: i32 }
fn main() {
let n = 10_000_000;
let mut points = Vec::with_capacity(n);
for i in 0..n {
points.push(Point { x: i as i32, y: i as i32 });
}
let sum: i64 = points.iter().map(|p| (p.x + p.y) as i64).sum();
println!("Sum: {}, Count: {}", sum, points.len());
}
Goコード (go-mem/main.go)
package main
import "fmt"
type Point struct { X, Y int32 }
func main() {
n := 10_000_000
points := make([]*Point, n)
for i := 0; i < n; i++ {
points[i] = &Point{X: int32(i), Y: int32(i)}
}
var sum int64
for _, p := range points { sum += int64(p.X + p.Y) }
fmt.Printf("Sum: %d, Count: %d\n", sum, len(points))
}
実験3: 並列処理(素数計算)
マルチスレッド/GoroutineによるCPUコア活用効率。
速度比較
| 言語 | 実行時間 (real) | Rust vs Go (倍率) | 備考 |
|---|---|---|---|
| Rust (release) | 0.053s | 1.22倍 高速 | OSスレッドの直接利用 |
| Go | 0.065s | (基準) | 軽量スレッド(Goroutine) |
Rustコード (rust-parallel/src/main.rs)
use std::thread;
fn is_prime(n: u64) -> bool {
if n <= 1 { return false; }
for i in 2..=((n as f64).sqrt() as u64) {
if n % i == 0 { return false; }
}
true
}
fn main() {
let limit = 1_000_000;
let num_threads = 4;
let chunk_size = limit / num_threads;
let mut handles = vec![];
for t in 0..num_threads {
let start = t * chunk_size + 1;
let end = if t == num_threads - 1 { limit } else { (t + 1) * chunk_size };
handles.push(thread::spawn(move || {
let mut sum = 0;
for i in start..=end { if is_prime(i) { sum += i; } }
sum
}));
}
let total_sum: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum();
println!("Total Sum: {}", total_sum);
}
Goコード (go-parallel/main.go)
package main
import ("fmt"; "math"; "sync")
func isPrime(n uint64) bool {
if n <= 1 { return false }
limit := uint64(math.Sqrt(float64(n)))
for i := uint64(2); i <= limit; i++ { if n%i == 0 { return false } }
return true
}
func main() {
limit := uint64(1_000_000); numWorkers := 4; chunkSize := limit / uint64(numWorkers)
var wg sync.WaitGroup; sums := make(chan uint64, numWorkers)
for t := 0; t < numWorkers; t++ {
wg.Add(1); start := uint64(t)*chunkSize + 1; end := uint64(t+1) * chunkSize
if t == numWorkers-1 { end = limit }
go func(s, e uint64) {
defer wg.Done(); var pSum uint64
for i := s; i <= e; i++ { if isPrime(i) { pSum += i } }
sums <- pSum
}(start, end)
}
wg.Wait(); close(sums); var total uint64
for s := range sums { total += s }
fmt.Printf("Total Sum: %d\n", total)
}
実験4: 文字列操作(結合)
バッファ確保を伴う文字列処理の効率。
速度比較
| 言語 | 実行時間 (real) | Rust vs Go (倍率) | 備考 |
|---|---|---|---|
| Rust (release) | 0.004s | 1.25倍 高速 | String::with_capacity |
| Go | 0.005s | (基準) | strings.Builder |
Rustコード (rust-string/src/main.rs)
fn main() {
let n = 1_000_000;
let mut s = String::with_capacity(n * 5);
for _ in 0..n { s.push_str("hello"); }
println!("Length: {}", s.len());
}
Goコード (go-string/main.go)
package main
import ("fmt"; "strings")
func main() {
n := 1_000_000; var b strings.Builder; b.Grow(n * 5)
for i := 0; i < n; i++ { b.WriteString("hello") }
fmt.Printf("Length: %d\n", len(b.String()))
}
実験5: JSON処理(Serde vs Stdlib)
シリアライズ・デシリアライズのスループット。
速度比較
| 言語 | 実行時間 (real) | Rust vs Go (倍率) | 備考 |
|---|---|---|---|
| Rust (release) | 0.034s | 3.38倍 高速 | コンパイル時コード生成 |
| Go | 0.115s | (基準) | 実行時リフレクション |
Rustコード (rust-json/src/main.rs)
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct User { id: u32, name: String, email: String, active: bool }
fn main() {
let n = 100_000;
let mut users = Vec::with_capacity(n);
for i in 0..n {
users.push(User { id: i, name: format!("User{}", i), email: format!("u{}@ex.com", i), active: i % 2 == 0 });
}
let json = serde_json::to_string(&users).unwrap();
let decoded: Vec<User> = serde_json::from_str(&json).unwrap();
println!("Decoded count: {}", decoded.len());
}
Goコード (go-json/main.go)
package main
import ("encoding/json"; "fmt")
type User struct { ID uint32; Name, Email string; Active bool }
func main() {
n := 100_000; users := make([]User, n)
for i := 0; i < n; i++ {
users[i] = User{ID: uint32(i), Name: fmt.Sprintf("U%d", i), Email: "u@ex.com", Active: i%2 == 0}
}
data, _ := json.Marshal(users)
var decoded []User; json.Unmarshal(data, &decoded)
fmt.Printf("Decoded count: %d\n", len(decoded))
}
3. 総合的な考察 (PREP)
Point (結論)
全体として、RustはGoに対して平均して約2.3倍高速であり、メモリ管理やデータ変換においては最大4倍の性能差を示しました。
Reason (理由)
- 決定論的メモリ管理: RustはGCがないため、実行時のオーバーヘッドが極小化されます。
- コンパイル時コード生成: JSON処理に見られるように、リフレクションを排除した設計が高速化に寄与しています。
- LLVMの最適化能力: Rustは強力な最適化バックエンドを最大限に活用します。
Example (具体例)
実験2(メモリ生成)での 4.02倍 という差は、Rustの「スタック/インライン配置」とGoの「ヒープ/ポインタ管理」の設計思想の差が最も顕著に現れた結果です。
感想
RustもGoも優れた言語ですが、特にパフォーマンスが重要な場面ではRustの方が有利であることが明確に示されました。Goは開発の迅速さやシンプルさが魅力ですが、性能を追求する場合はRustを選択する価値があります。
私は日頃パフォーマンス改善タスクに携わることが多いので、Rustも魅力的だなぁと感じました。
しかし業務を考えると、Goの方がシンプルな実装故に開発効率が高いケースも多いので、プロジェクトの要件に応じて適切な言語を選択することが重要だと思いますね。
あと、Rustはまだまだ現場が少ない印象なので、もう後数年は待つかなぁといった感じです。
とはいえRustは素晴らしい言語だと今回の検証で理解できたので、これからも注目していきたいと思います!!