【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が最速な理由

  1. ネットワークI/O特化の設計

    • Goのランタイムはネットワーク処理に特化して最適化されている
    • net/httpパッケージの効率的な実装
  2. 軽量なガベージコレクション

    • 短時間実行ではGCのオーバーヘッドが最小限
    • メモリ使用量も効率的
  3. AWS SDK Go v2の最適化

    • 成熟したSDKによる効率的なHTTPクライアント実装
    • JSON処理の最適化

Rustが予想より遅い理由

興味深いことに、RustのCPU使用時間は0.21秒と効率的なのですが、実経過時間は1.109秒と長くなっています。

CPU処理効率vs実経過時間の違い:

  1. CPU処理は効率的(0.21秒)

    • ゼロコスト抽象化による最適化
    • 効率的なメモリ使用
  2. しかし実経過時間は長い(1.109秒)

    • ネットワーク待機時間が約0.89秒
    • I/O処理での非効率性

具体的な要因:

  1. Tokio非同期ランタイムのオーバーヘッド

    • 単純なHTTPリクエストに対する非同期処理の重さ
    • イベントループの初期化コスト
  2. AWS SDK for Rustの最適化不足

    • 比較的新しいSDK(2021年以降)
    • HTTPクライアントの最適化が発展途上
  3. 型安全性と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

まとめ

今回の検証で得られた重要な洞察:

  1. 「Rust最速」は用途次第

    • CPU集約的処理ではRustが有利
    • ネットワークI/O処理ではGoが優位
    • 実経過時間とCPU効率は別物
  2. timeコマンドの正しい読み方

    • total時間 = ユーザー体感速度
    • user + system時間 = プログラム効率
    • I/Oバウンドな処理では両者に大きな差が生まれる
  3. 言語特性とエコシステムの重要性

    • Goはネットワーク処理に特化した設計
    • SDK成熟度が実際の性能に大きく影響
    • 理論値と実測値は異なる場合がある
  4. 実測の大切さ

    • ベンチマークは実際の用途に近い条件で行うべき
    • 一般的な認識や理論を鵜呑みにせず検証が重要

結論として、AWS API操作のようなネットワークI/O中心の処理では、Goが実用的な最適解であることが実証されました。

ただし、RustもCPU効率では優秀な結果を示しており、処理内容によって最適な言語は変わることも明らかになりました。

皆さんも自分の用途で実際に測定してみることをお勧めします!


💬 他のAWSサービスでの比較結果があれば、コメントで教えてください!

🔄 続編として、S3操作やLambda実行時間の比較も検討中です。

参考資料

Discussion