プログラミング言語の速度比較を正しく行う方法

· · Benchmark, Performance, C#, C++, Java, Go

一番大事なこと

「どの言語が最速か」を1本の数字で決めようとしない。 そうではなく、「どのworkloadを、どの条件で、どの指標で比較するか」を明確にする。

まず決めるべきこと

「速い」という言葉だけでは不十分。以下を明確にする。

1. 起動時間を見たいのか

CLIツールや短命バッチなら、cold startやprocess startupが重要。JITの初期化コストを含むかどうかで結果が大きく変わる。

2. 長時間運転のthroughputを見たいのか

サーバーや常駐プロセスなら、warm-up後の定常状態が主題。初回の遅さは本質ではない。

3. tail latency(p95/p99)を見たいのか

APIやUIでは、平均よりp95/p99の方が重要。平均が速くても、たまに大きく止まるなら困る。

4. メモリ効率も見たいのか

CPU時間だけでなく、最大メモリ使用量、割り当て回数、GC回数、GC pauseも見ないと実運用の重さは読み違える。

言語比較の難しさ

JITとAOTの違い

  • C#とJavaは通常JIT(実行時コンパイル)
  • C++とGoは通常AOT(事前コンパイル)

初回実行を測ればランタイム起動コスト込みになる。warm-up後のみを見れば定常状態の最適化が効く。coldとwarmは別物として扱う。

言語差より実装差が大きいことが多い

同じ「ソート」でも、標準ライブラリか自前実装か、余分なコピーがあるかで結果が変わる。JSONや暗号処理は言語そのものよりライブラリ実装の差が効く。

C++は最適化で処理が消える

コンパイラが「この計算結果、誰も使ってない」と判断して処理を消すことがある。結果の使用やchecksum出力が重要。

GC(ガベージコレクション)の扱い

「GCがあるから遅い」は雑すぎる。大量短命オブジェクトのさばき方、ヒープサイズ設定、GCの頻度とpauseの方が効く。

比較でやってはいけないこと

  1. DebugとReleaseを混ぜる - 必ず本番相当の最適化ビルドで
  2. 同じ問題を解いていない - 入力形式や出力が違うと要件差を測ってしまう
  3. 1回だけ実行して結論 - JIT、CPUブースト、熱、GCが全部混ざる
  4. warm-upを混ぜる - coldとwarmは別表に分ける
  5. 正しさ確認をしない - 全実装で同じchecksumが出ることを確認
  6. 1本のmicrobenchmarkだけで決める - tight loopで勝っても実サービスで勝つとは限らない

比較の基本方針:2層構成

層1: 言語内の測定(各言語のツールを使う)

言語 推奨ツール
C# BenchmarkDotNet
Java JMH
Go go test -bench + benchstat
C++ Google Benchmark

これらは各言語のランタイム事情や統計処理を面倒見てくれる。言語内の比較や実装の掘り下げに有効。

層2: 言語横断の比較(共通ランナーを使う)

BenchmarkDotNetの結果とJMHの結果をそのまま横に並べるのは危ない(ハーネスの作法が違うため)。

各実装を同じCLI契約で呼べる実行ファイルにして、外側から同じ条件で回す。

bench --scenario sort_int32 --dataset data/sort_10m.bin --mode warm
bench --scenario group_words --dataset data/words_100mb.txt --mode cold

共通ランナー側で:

  • 実行順序をランダム化
  • cold/warmを分ける
  • 同じデータセットを渡す
  • checksumを検証
  • wall-clockとメモリを採る

おすすめのベンチ項目(3〜4本)

1. sort_int32_10m

目的: CPU + メモリ帯域。固定seedで生成した1000万件のint32をソート。

2. hash_group_count

目的: ハッシュテーブル、文字列処理、割り当て、GCの傾向。テキストデータの単語出現回数を数える。

3. parallel_sha256

目的: 並列処理、スケジューラの癖。Nスレッドでハッシュ化し、スレッド数1/2/4/8で段階測定。

4. startup_noop または startup_parse_small

目的: 起動時間。noop(起動して即終了)と小さな入力を1回処理して終了の2パターン。

JSONやHTTPベンチは実務に近いが、言語比較というよりライブラリ・フレームワーク込みの比較になる。その旨を明記する。

言語ごとに揃える条件

C++

  • 最適化ビルド(-O3//O2、LTO、PGO)
  • コンパイラとSTL実装を固定
  • 結果が最適化で消えないように注意
  • 未定義動作で速く見えていないか疑う

C#

  • Releaseビルド
  • .NETバージョンを固定
  • Server GC / Workstation GCを記録
  • Tiered Compilation、ReadyToRun、Native AOTの有無を明記
  • JITのC#とNative AOTのC#は別軸

Java

  • JDKのベンダーとバージョンを固定
  • GCを明記(G1、ZGCなど)
  • ヒープサイズやJVMオプションを記録
  • warm-up/measurement/forkの回数を固定

Go

  • Goバージョンを固定
  • GOMAXPROCSを固定
  • GOGCをいじるなら記録
  • cgoの利用有無を明記

実行環境の揃え方

  • 同じCPU/メモリ/ストレージ
  • 同じOSバージョン
  • 同じ電源設定(ノートPCはAC接続かバッテリーかだけでも別世界)
  • 同じ室温に近い条件
  • バックグラウンド処理(更新、ウイルススキャン)を抑える
  • A/B/A/Bのように交互に回す(熱やスロットリングの偏りを減らす)

測るべき指標

  1. wall-clock time - ユーザーが待つ実時間。最初に見るべき指標
  2. CPU time - 実際にCPUを使った時間
  3. メモリ/割り当て - 最大RSS、総割り当て量、GC回数、GC pause
  4. 分布 - 中央値、p95/p99、最小/最大、標準偏差

平均だけで語ると、たまに飛ぶ処理の正体が見えない。

結果の読み方の注意

  • 初回だけC#/Javaが遅い → JITの影響。起動時間が重要か、長時間運転が主題かで評価が分かれる
  • C++がtight loopで強い → 低レベル最適化の効果。ただしそれが実サービス全体の速さを保証するわけではない
  • C#/Javaがsteady-stateで追いつく・逆転する → JIT最適化の効果。珍しいことではない
  • allocation-heavyな処理で差が大きい → 言語名よりメモリレイアウトやGC挙動の差が効いている

記録テンプレート

最低限以下を残す:

timestamp, language, scenario, run_kind, cold_or_warm, elapsed_ms,
cpu_ms, max_rss_mb, alloc_bytes, gc_count, checksum,
compiler_or_runtime, compiler_version, flags, os, cpu, threads, input_id, notes

ベンチは測ることより、後から解釈できることの方が大事。

まとめ

  1. 起動時間と定常状態を分ける
  2. 同じアルゴリズム、同じ入力、同じ正しさ確認で測る
  3. 1本のベンチだけで結論を出さない
  4. 言語内のbenchmarkと言語横断のbenchmarkを分ける
  5. 平均より中央値と分布を見る
  6. 条件とraw dataを残す
  7. 言語名で勝敗を決めようとしすぎない

現実の性能は「言語+ランタイム+ライブラリ+ビルド条件+データ+OS+ハードウェア」の合わせ技で決まる。

関連する記事

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

共有メモリの安全な使い方

共有メモリを実務で扱うときの典型的な落とし穴を整理し、同期、可視性、寿命、ABI、権限、クラッシュ復旧まで踏まえた事故率の低い設計の出発点を、Windows と POSIX の両方の API を交えながら具体的に示します。

記事を読む

Windows上でプログラムの実行速度を正しく比較する

Windowsで自作プログラムの新旧バージョンの実行速度を公平に比較するための実践手順をまとめた記事です。電源モードや熱、バックグラウンドノイズの揃え方、wall-clockやCPU時間などの指標選び、A/B交互実行、ETW/WPRでの掘り下げまで、再現性のあるベンチの作法...

記事を読む

Windowsシングルバイナリ化の限界と実践

Windows アプリを 1 EXE にしたいときの「配布物を 1 個にする」と「OS 依存を消す」の違いを、.NET、C++、WebView2、WinUI、サービス、ドライバまで段階別に整理し、技術選定と配布設計の判断軸が分かります。

記事を読む

関連トピック

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

このテーマがつながるサービス

この記事は次のサービスページにつながります。近い入口からご覧ください。

ブログ一覧に戻る