【2025年版】AWS SDK性能検証: Go vs Rust vs Python - "Rust最速神話"を実測で検証してみた
はじめに
「Rustは最速」「GoはRustより遅い」- こんな話をよく耳にしませんか?
AWSクラウドエンジニアとして日々AWS SDKを使っていると、実際のところどの言語が一番速いのか? という疑問が湧いてきました。特にネットワークI/Oが中心となるAWS API操作では、言語間の性能差はどう現れるのでしょうか。
そこで今回、Go・Rust・Pythonの3言語でAWS EC2の DescribeSecurityGroups
APIを実装し、実際に実行速度を測定してみました。
結果は...まさかの展開でした。
TL;DR(結論先出し)
順位 | 言語 | 実経過時間 | CPU時間 | CPU使用率 |
---|---|---|---|---|
🥇 | Go | 0.653秒 | 0.02秒 | 4% |
🥈 | Rust | 1.109秒 | 0.21秒 | 18% |
🥉 | Python | 1.318秒 | 0.74秒 | 56% |
実経過時間でGoが圧勝という、一般的な認識を覆す結果になりました。
ただし、CPU効率ではRustも健闘しており、興味深い結果となっています。
検証環境・条件
測定対象
- 処理内容: EC2セキュリティグループ一覧取得(3件のセキュリティグループを取得)
- AWS リージョン: ap-northeast-1(東京)
-
測定方法:
time
コマンドによる実行時間測定
実行環境
- OS: macOS
- Go: 1.21+
- Rust: 1.75+
- Python: 3.11+
測定条件
重要なポイントとして、事前コンパイル済みバイナリで測定しました:
# コンパイル時間を除外した公正な比較
cargo build --release # Rust
go build main.go # Go
# Pythonはインタープリター実行
実装コード比較
Go実装
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("unable to load SDK config: %v", err)
}
client := ec2.NewFromConfig(cfg)
if err := describeSecurityGroups(client); err != nil {
log.Fatalf("failed to describe security groups: %v", err)
}
}
func describeSecurityGroups(client *ec2.Client) error {
output, err := client.DescribeSecurityGroups(context.TODO(), &ec2.DescribeSecurityGroupsInput{})
if err != nil {
return fmt.Errorf("API call failed: %w", err)
}
for _, group := range output.SecurityGroups {
printSecurityGroup(group)
}
return nil
}
func printSecurityGroup(group types.SecurityGroup) {
groupName := getStringValue(group.GroupName)
groupID := getStringValue(group.GroupId)
vpcID := getStringValue(group.VpcId)
description := getStringValue(group.Description)
fmt.Printf("Found Security Groups %s (%s), vpc id %s and description %s\n",
groupName, groupID, vpcID, description)
}
func getStringValue(s *string) string {
if s == nil {
return "unknown"
}
return *s
}
Rust実装
use aws_sdk_ec2;
use std::time::Instant;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = aws_config::load_from_env().await;
let client = aws_sdk_ec2::Client::new(&config);
describe_security_groups(&client, vec![]).await;
Ok(())
}
async fn describe_security_groups(client: &aws_sdk_ec2::Client, group_ids: Vec<String>) {
let response = client
.describe_security_groups()
.set_group_ids(if group_ids.is_empty() { None } else { Some(group_ids) })
.send()
.await;
match response {
Ok(output) => {
for group in output.security_groups() {
println!(
"Found Security Group {} ({}), vpc id {} and description {}",
group.group_name().unwrap_or("unknown"),
group.group_id().unwrap_or("id-unknown"),
group.vpc_id().unwrap_or("vpcid-unknown"),
group.description().unwrap_or("(none)")
);
}
}
Err(err) => {
eprintln!("Error listing EC2 Security Groups: {:?}", err);
}
}
}
Python実装
import boto3
import time
def describe_security_groups():
ec2 = boto3.client('ec2')
try:
response = ec2.describe_security_groups()
for group in response['SecurityGroups']:
print(f"Found Security Group {group['GroupName']}, "
f"vpc id {group.get('VpcId', 'unknown')} "
f"and description {group.get('Description', 'none')}")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
describe_security_groups()
測定結果詳細
実行時間測定
# Go実行結果
$ time ./main
Found Security Groups default (sg-056e444c1891b25f7), vpc id vpc-0c896a4cf4a0ea47e and description default VPC security group
Found Security Groups launch-wizard-1 (sg-0aa114dbcfa7f4094), vpc id vpc-0c896a4cf4a0ea47e and description launch-wizard-1 created 2025-06-23T19:28:45.143Z
Found Security Groups launch-wizard-2 (sg-09e4b82ddc89e99a7), vpc id vpc-0c896a4cf4a0ea47e and description launch-wizard-2 created 2025-06-23T19:39:03.390Z
./main 0.01s user 0.01s system 4% cpu 0.653 total
# Rust実行結果
$ time ./target/release/aws-operations
Found Security Group default (sg-056e444c1891b25f7), vpc id vpc-0c896a4cf4a0ea47e and description default VPC security group
Found Security Group launch-wizard-1 (sg-0aa114dbcfa7f4094), vpc id vpc-0c896a4cf4a0ea47e and description launch-wizard-1 created 2025-06-23T19:28:45.143Z
Found Security Group launch-wizard-2 (sg-09e4b82ddc89e99a7), vpc id vpc-0c896a4cf4a0ea47e and description launch-wizard-2 created 2025-06-23T19:39:03.390Z
./target/release/aws-operations 0.18s user 0.03s system 18% cpu 1.109 total
# Python実行結果
$ time python describe_security_groups.py
Found Security Group default, vpc id vpc-0c896a4cf4a0ea47e and description default VPC security group
Found Security Group launch-wizard-1, vpc id vpc-0c896a4cf4a0ea47e and description launch-wizard-1 created 2025-06-23T19:28:45.143Z
Found Security Group launch-wizard-2, vpc id vpc-0c896a4cf4a0ea47e and description launch-wizard-2 created 2025-06-23T19:39:03.390Z
python describe_security_groups.py 0.55s user 0.19s system 56% cpu 1.318 total
分析結果
指標 | Go | Rust | Python |
---|---|---|---|
実経過時間 | 0.653秒 | 1.109秒 | 1.318秒 |
CPU時間合計 | 0.02秒 | 0.21秒 | 0.74秒 |
ユーザーCPU時間 | 0.01秒 | 0.18秒 | 0.55秒 |
システムCPU時間 | 0.01秒 | 0.03秒 | 0.19秒 |
CPU使用率 | 4% | 18% | 56% |
重要な洞察:実経過時間 vs CPU時間
ここで興味深い発見があります:
実経過時間(ユーザー体感): Go > Rust > Python
CPU効率(プログラム処理): Go > Rust > Python
RustはCPU使用時間は0.21秒と効率的ですが、実経過時間は1.109秒かかっています。この差分約0.89秒は主にネットワーク待機時間です。
つまり:
- Go: ネットワーク待機も含めて最速
- Rust: CPU処理は効率的だが、I/O待機で時間をロス
- Python: CPU処理もI/O処理も相対的に非効率
なぜこの結果になったのか?
timeコマンドの読み方
まず、time
コマンドの出力について理解しておきましょう:
./target/release/aws-operations 0.18s user 0.03s system 18% cpu 1.109 total
- user (0.18s): プログラムがCPU処理に使った時間
- system (0.03s): システムコール(ファイルI/O、ネットワーク等)に使った時間
- total (1.109s): 実際の経過時間(ユーザーが体感する時間)
- cpu (18%): CPU使用率
重要なのはtotal
の時間です。これが実際にユーザーが待つ時間になります。
Goが最速な理由
-
ネットワークI/O特化の設計
- Goのランタイムはネットワーク処理に特化して最適化されている
-
net/http
パッケージの効率的な実装
-
軽量なガベージコレクション
- 短時間実行ではGCのオーバーヘッドが最小限
- メモリ使用量も効率的
-
AWS SDK Go v2の最適化
- 成熟したSDKによる効率的なHTTPクライアント実装
- JSON処理の最適化
Rustが予想より遅い理由
興味深いことに、RustのCPU使用時間は0.21秒と効率的なのですが、実経過時間は1.109秒と長くなっています。
CPU処理効率vs実経過時間の違い:
-
CPU処理は効率的(0.21秒)
- ゼロコスト抽象化による最適化
- 効率的なメモリ使用
-
しかし実経過時間は長い(1.109秒)
- ネットワーク待機時間が約0.89秒
- I/O処理での非効率性
具体的な要因:
-
Tokio非同期ランタイムのオーバーヘッド
- 単純なHTTPリクエストに対する非同期処理の重さ
- イベントループの初期化コスト
-
AWS SDK for Rustの最適化不足
- 比較的新しいSDK(2021年以降)
- HTTPクライアントの最適化が発展途上
-
型安全性とI/O処理のトレードオフ
- コンパイル時最適化は主にCPU処理に効果
- ネットワークI/Oでは型安全性のオーバーヘッドが残る
Pythonについて
インタープリター言語としては健闘していますが、boto3の多くの処理がC拡張で実装されているため、純粋なPython処理ではない点が興味深いところです。
実用的な言語選択指針
この結果を踏まえた、実務での言語選択指針:
AWS CLI/API操作ツール → Go
- 最速実行時間
- 最小リソース使用
- シングルバイナリ配布
- クロスプラットフォーム対応
システムプログラミング → 用途次第
- CPU集約的処理: Rust
- ネットワーク集約的処理: Go
- メモリ安全性重視: Rust
プロトタイピング/スクリプト → Python
- 開発速度重視
- 豊富なライブラリエコシステム
- 実行速度は妥協
マイクロサービス/API開発 → Go
- 高いスループット
- 低レイテンシ
- 効率的なリソース使用
参考:コンパイル時間込みの比較
参考として、初回実行時(コンパイル時間込み)の結果も掲載しておきます:
言語 | 初回実行時間 | 備考 |
---|---|---|
Go | 2.934秒 |
go run でのコンパイル込み |
Rust | 8.887秒 |
cargo run でのコンパイル込み |
Python | 1.318秒 | インタープリター実行 |
この場合はPythonが最速になりますが、継続的に実行するツールでは事前コンパイルが現実的です。
検証の再現方法
この検証を再現したい方のために、測定手順を記載しておきます:
1. 各言語での実装
上記のコードを各言語で実装してください。
2. 事前コンパイル
# Go
go build -o main main.go
# Rust
cargo build --release
# Python(コンパイル不要)
3. 実行時間測定
# Go
time ./main
# Rust
time ./target/release/project-name
# Python
time python script.py
4. 複数回測定して平均を取る
# 例:3回実行して平均を取る
for i in {1..3}; do echo "Run $i:"; time ./main; echo; done
まとめ
今回の検証で得られた重要な洞察:
-
「Rust最速」は用途次第
- CPU集約的処理ではRustが有利
- ネットワークI/O処理ではGoが優位
- 実経過時間とCPU効率は別物
-
timeコマンドの正しい読み方
-
total
時間 = ユーザー体感速度 -
user + system
時間 = プログラム効率 - I/Oバウンドな処理では両者に大きな差が生まれる
-
-
言語特性とエコシステムの重要性
- Goはネットワーク処理に特化した設計
- SDK成熟度が実際の性能に大きく影響
- 理論値と実測値は異なる場合がある
-
実測の大切さ
- ベンチマークは実際の用途に近い条件で行うべき
- 一般的な認識や理論を鵜呑みにせず検証が重要
結論として、AWS API操作のようなネットワークI/O中心の処理では、Goが実用的な最適解であることが実証されました。
ただし、RustもCPU効率では優秀な結果を示しており、処理内容によって最適な言語は変わることも明らかになりました。
皆さんも自分の用途で実際に測定してみることをお勧めします!
💬 他のAWSサービスでの比較結果があれば、コメントで教えてください!
🔄 続編として、S3操作やLambda実行時間の比較も検討中です。
Discussion