テクノロジー

AIに「最速のコード」を書かせるなら何語か――アセンブリ・C・Rust・Goで重い課題を作らせ、保守までさせた

管理者より

AIにコードを書かせるのが当たり前になってきた。では、人間が書くことを考えずに「とにかく速く、しかも壊れないプログラム」を求めるなら、AIにはどの言語で書かせるのがよいのか。アセンブリならコンパイラを超えられるのか。Cは本当に危ないのか。RustとGoならどちらなのか。

気になったので、重い計算をする課題を3つ用意して、AIに4つの言語で作らせてみた。作らせて終わりにせず、あとから機能を追加させる「保守」まで試している。結果を共有する。

3つのお題について、4言語のプログラムが最速の言語の何倍の時間がかかったかを示す横棒グラフ。perftの1スレッドはRust・Go・アセンブリが同着でCが1.30倍、14スレッドはGoが最速。gzip圧縮の1スレッドはアセンブリが最速でGoが1.45倍、14スレッドはRustが最速でGoが1.47倍。レイトレーサーは1スレッド・14スレッドともRustが最速で、Goは2.77倍と2.85倍

図1 お題ごとに、最速の言語の何倍の時間がかかったか。1.00倍がそのお題の最速。プログラムはすべて Claude Opus 5.5 が作成。編集部の実験

結論を先に

使ったAIは、Anthropic の Claude Opus 5.5 の1種類である。プログラムの作成も、あとからの機能追加も、すべてこのモデルに行わせた。

問い答え
最も速かった言語は?Rust。3つのお題すべてで最速か最速グループに入った
手書きのアセンブリはCより速いか?速くならなかった。Cと同程度か、遅い
壊れないプログラムになったか?12本すべてが隠しテストに合格した。実行時の検査でも問題は見つからなかった
保守しやすい言語は?AIが保守する限り、差はほとんど出なかった。アセンブリでさえ既存機能を壊さずに機能を追加できた
Cの弱点は?完成品のバグではなく、バグを見つける仕組みが薄いこと
RustとGoのどちらを選ぶか?計算が重く速さが要件ならRust、それ以外はGo。用途別の判断は最後の節にまとめた

ただし、どの数字も各言語・各お題で1回ずつの試行である。速さの大きな差は信頼できるが、作業時間のような小さな差は誤差の範囲にとどまる可能性がある。

実験のやり方

3つのお題

重い計算をし、細部を間違えやすく、そして正解を厳密に確かめられる課題を選んだ。

お題何をするかなぜ選んだか
perftチェスの局面から、指せる手の組み合わせを全部数えるキャスリング(王と飛車に当たる駒を同時に動かす特殊な手)やアンパッサンなど例外的なルールが多く、1つでも漏れると数が合わない。正解は公開されている(初期局面から7手先までなら 3,195,901,860 通り)
gzipデータ圧縮の標準形式 gzip(RFC 1952、中身の圧縮方式は RFC 1951)の圧縮器と展開器を、ライブラリを使わずに自作する壊れたファイルを渡されても落ちない堅牢さが問われる。圧縮して展開すると元に戻るかで正しさを確かめられる
レイトレーサー約400万枚の三角形でできた3Dの風景を、光の反射や影まで計算して描く空間を区切って探す工夫がないと終わらない計算量。描いた画像を正解の画像と比べて確かめられる

3つめのお題で描かせた画像はこれである。鏡のような球には周りの景色が映り込み、地形には影が落ちる。

レイトレーサーのお題で描かせる3Dの風景。緑の丘と雪をかぶった山の地形に湖があり、その上に色とりどりの球が多数浮かんでいる。鏡のような球には周りの景色が映り込んでいる

図2 レイトレーサーのお題の正解画像(縮小)。三角形約400万枚と球400個からなる。編集部の実験

条件

  • AI:Anthropic の Claude Opus 5.5(モデルID claude-opus-5-5)。コーディング用のコマンドライン版である Claude Code から動かし、思考の深さを指定する設定(effort)は「高」(high)にした。人間は一切介入せず、1回の作業は最長3時間とした。
  • 言語:アセンブリ、C、Rust、Go。アセンブリは、測定機のCPU(ARM系の AArch64)向けに、すべてをアセンブリだけで書かせた。
  • 共通のルール:外部ライブラリは使わず、各言語の標準ライブラリだけで書く。インターネット上の既存実装は参照させない。
  • 検証:AIには公開テストだけを渡し、採点はAIに見せていない隠しテストで行った。1件でも誤れば不合格とした。
    • perft:471局面
    • gzip:手作りの境界ケース34件と、正しいファイルを壊した入力4,000件、1GiBのデータの往復
    • レイトレーサー:小さなシーン60件と、大きなシーン1件
  • 速さ:測定機(14コアのCPU)で、1スレッド(1つのコアだけで計算する)と14スレッド(14コアに分担させる)の両方で測った。数値の絶対値は機材で変わるので、言語どうしの比で見てほしい。

AIのサービス側の利用上限で作業が途中で止まった回は、条件がそろわないので結果から外し、最初からやり直した。

結果1:速さはRust、ただし差は課題による

お題アセンブリCRustGo
perft 1スレッド(秒)0.470.610.470.47
perft 14スレッド(秒)1.281.431.491.17
gzip 圧縮 1スレッド(MB/秒)185176183128
gzip 圧縮 14スレッド(MB/秒)1,4761,4571,6441,119
gzip 展開(MB/秒)1,2901,4111,223974
レイトレーサー 1スレッド(秒)5.154.073.419.43
レイトレーサー 14スレッド(秒)0.560.450.391.11

perft は1スレッドで約7.6億、14スレッドで約220億の局面を数える。gzip の圧縮率は4言語とも標準の zlib(レベル6)の0.99〜1.00倍で、速さだけでなく縮み方も本家並みだった。

いくつか目を引く点がある。

  • レイトレーサーでRustとGoの差が最も開いた(2.8倍)。小数の計算が密な処理ほど、Rust(とC)のコンパイラの最適化が効く。
  • perft の14スレッドではGoが最速だった。整数とビット演算が中心で、並列に分けやすい処理では、Goが不利にならない。
  • 参考までに、編集部が書いた素朴なC実装でレイトレーサーを描くと、1スレッドで18.3秒かかった。AIの作ったプログラムは、どの言語でもこれより2〜5倍速い。

結果2:アセンブリは速くならなかった

「人間には無理でも、AIならコンパイラを超えるアセンブリを書けるのでは」という期待は外れた。アセンブリは perft と gzip の1スレッドで最速に並んだが、レイトレーサーではCより27%遅かった。

それでも、アセンブリの3本は隠しテストにすべて合格している。数千行のアセンブリ(perft 1,635行、gzip 3,547行、レイトレーサー 4,374行)を最後まで書き切る力は十分にあった。問題は力ではなく、得るものがないことである。

さらに、アセンブリには移植性の問題がある。今回のアセンブリは、CPUの命令だけでなく、測定機のOS固有の書き方にも依存していた。

お題OS固有のアドレスの書き方OSのライブラリの呼び出し
perft24か所14か所
gzip6か所43か所
レイトレーサー2か所38か所

CPUの種類が違えば全面的な書き直しになり、CPUが同じでもOSが違えば、表の箇所をすべて直す必要がある。C・Rust・Goなら、基本的には作り直す(ビルドし直す)だけで済む。

結果3:保守でも、言語の差はほとんど出なかった

作って終わりではなく、保守もさせた。AIが作った12本のプログラムに、同じ Claude Opus 5.5 の別のセッションが機能を追加する。前の作業の記憶は引き継がないので、手がかりはコードと残されたメモだけである。追加する機能は、既存の仕組みに深く手を入れないと実現できないものを選んだ。

お題追加させた機能手を入れる必要がある場所
perftChess960(駒の初期配置がランダムなチェス)への対応。正解は公開値で確かめた計算で作成キャスリングの処理を根本から作り直す
gzipzlib 形式(RFC 1950)の追加と、圧縮レベル1〜9の指定入出力の枠組み、チェックサム、圧縮の探索の強さ
レイトレーサー無限に広がる平面と市松模様の材質の追加交差の判定、影、色の決め方。無限の物体は空間の区切り方の前提を崩す

前のAIが残したメモや検証用の道具も、作業ディレクトリごと引き継がせた。

3つの機能追加について、4言語それぞれの作業時間を示す横棒グラフ。perftのChess960対応はRust 10分、アセンブリ17分、C 21分、Go 26分。gzipのzlib形式と圧縮レベルの追加はアセンブリ54分、C 64分、Rust 82分、Go 89分。レイトレーサーの平面と市松模様の追加はGo 14分、アセンブリ15分、C 17分、Rust 19分

図3 機能追加にかかった時間。作業はすべて Claude Opus 5.5。12本すべてが追加機能と既存機能の隠しテストに合格した。編集部の実験

12本すべてが、追加機能の隠しテストと、既存機能が壊れていないかを確かめる隠しテストの両方に合格した。 既存機能の速さもほとんど落ちず、最も落ちたのは Rust の gzip(14スレッドの圧縮)で6%だった。

言語作業時間の合計3つの保守の順位(速い順)
アセンブリ86分2位、1位、2位
C102分3位、2位、3位
Rust111分1位、3位、4位
Go129分4位、4位、1位

順位は課題ごとに入れ替わり、一貫した傾向は見えない。1回ずつの試行では、言語の差より、実行ごとのばらつきのほうが大きいと考えるのが妥当だろう。

見た目の「保守しやすさ」は当てにならなかった

保守をさせる前に、コードの見た目の指標も測っていた。関数の大きさ、分岐の複雑さ、コメントの量、各言語の検査ツールの警告である。この指標では、アセンブリが最も保守しにくい言語だった。関数1つの大きさ(中央値)が、他の言語の1.6〜15倍あったからである。

ところが、実際の保守ではアセンブリの作業時間が最も短かった。人間向けの「読みやすさ」の指標は、AIによる保守のしやすさを予測しなかったことになる。手がかりがあるとすれば、作成時のAIがアセンブリに多めのコメントを残していたことだ(コメント行の割合は5.9〜10.7%で、他の言語は2.3〜7.5%)。

ただし、これはAIが読めるという話である。人間がアセンブリを確認し、直せるかは別の問題だ。

Cは劣るのか――弱点は「安全網の薄さ」

Cは「危ない言語」と言われる。今回のデータで確かめると、話はもう少し込み入っていた。

完成品には、どの言語にも問題が残っていなかった

完成したプログラムを、各言語の実行時の検査機能を有効にしてビルドし直し、テストの入力を流した。

言語使った検査見つかった問題
Cメモリの不正なアクセスと未定義動作(言語仕様が結果を定めていない危険な操作)の検出、スレッド間の競合の検出0件
Rust整数のあふれの検出0件
Goスレッド間の競合の検出0件

速さでも、Cはレイトレーサーで2位、gzip の展開で1位である。成果物だけを見る限り、Cは劣っていない。

違いは、作る過程に出ていた

それでもCには構造的な弱点がある。今回のデータでは、それが次の3つの形で表れた。

1. コンパイラが黙っている。 完成したCのコードは、一般的な警告の設定では3本とも警告0件だった。追加の設定をすると、perft だけで符号の暗黙の変換が9件見つかる。Cは危険になりうる書き方の多くを、言語仕様として許している。Rustなら、こうした暗黙の型変換はそもそもコンパイルが通らない。

2. 実行時の検査が、最初から有効になっていない。 配列の範囲外を読んだとき、RustとGoは必ず検出し、ファイル名と行番号を示して止まる。Cは、クラッシュするか、何事もなく不正な値を読んで先へ進む。作成中のログには、その差がそのまま残っていた。

  • C版の gzip では、先読みの処理がデータの末尾を越えて読み、プログラムが異常終了した。このときはたまたま落ちたので気づけたが、この種のバグは、落ちずに不正な値を読んだまま進むことのほうが多い。
  • Rust版の gzip では、異常は「deflate.rs の53行目」のように、場所が特定された形で止まっていた。

3. 検査の道具が標準ではなく、使われていない。 作成と保守をあわせた各言語6回の作業で、AIが自分から検査の道具を使った回数を数えた。

言語AIが自分から使った検査
Go静的な検査(go vet)131回、テスト(go test)119回、競合の検出4回
Rustテスト(cargo test)23回、静的な検査(clippy)2回
Cメモリ検査の道具が合計3回

GoとRustは、テストや検査がコマンド1つで使える標準の道具になっている。AIも日常的に回していた。Cの検査は、コンパイラの設定を自分で組み替えないと使えず、AIはほとんど使わなかった。

今回のCの完成品がきれいだったのは、AIが大量のテストを自作して補ったからである。Cの問題は、バグが多いことではなく、バグがあったときに見つかる保証が弱いことにある。 検証をAIに任せきりにする場面ほど、この差は重くなる。

RustとGo、どちらを選ぶか

ここまでで、候補はRustとGoに絞られた。両者を、速さ以外の観点も含めて比べる。

測ったこと

観点RustGo
速さ(小数の計算が密な処理)2.8倍速い(レイトレーサー)
速さ(並列度の高い整数の処理)1.27倍速い(perft の14スレッド)
作成と保守の合否すべて合格すべて合格
作成時間・保守時間の合計279分・111分239分・129分
1ファイルを直した後の再ビルド2.6〜4.2秒0.1〜0.2秒
実行ファイルの大きさ0.5〜0.7MB1.4〜2.8MB
最大メモリ(gzip 14スレッド)28MB51MB
最大メモリ(レイトレーサー)1,257MB848MB
標準の検査ツールの警告8〜32件すべて0件

意外だったこと:AIはGoのGC(ゴミ集め)を止めていた

Goには、使わなくなったメモリを自動で回収する仕組み(ガベージコレクション、GC)がある。今回、AIはGo版の3本すべてで、このGCを完全に止める設定を入れていた。Goの公式の文書にある、GCを実質的に無効にする設定である。

では、GCを有効に戻すとどれだけ遅くなるのか。測り直すと、perft と gzip は差がゼロ、レイトレーサーは約2%遅くなるだけだった。AIのコードは計算の中心でメモリをほとんど確保しないので、GCはもともとほぼ動いていなかった。Goがレイトレーサーで遅い理由はGCではなく、コンパイラの最適化の差と考えられる。

もう1つ見えたのは、速さを追うと、どちらの言語でも安全装置を外す方向に進むことだ。Rustは安全性の検査を部分的に外せる unsafe を13〜108か所で使い、Goも同じく unsafe を4〜58か所で使ったうえで、GCを止めていた。「Rustだから安全」「Goだから単純」という性質は、最速を目指した時点で薄まる。

用途別の判断

ここからは、今回の実験データと、各言語・各フレームワークの公開情報をもとにした判断である。お題以外の用途は実測していない。

既存の資産がないときにRustとGoのどちらを選ぶかの判断の流れ。計算が重く速さが要件に直結するならRust。GCを持ち込めない場合(ブラウザ、マイコン、他の言語から呼ぶ部品)もRust。1つのOS専用で標準の見た目のGUIが必要ならmacOSはSwift、WindowsはC#。それ以外の通信、連携、CLI、業務ツールはGo。GUIが要るならRustはTauri、GoはWailsを使う

図4 既存の資産がないときの選び方。実験結果と各フレームワークの公開情報をもとに編集部が作成

用途推奨理由
計算が重い処理(描画・圧縮・シミュレーション)Rust今回の実験で1.4〜2.8倍速い
並列度の高い整数の処理どちらでも1スレッドは同着、14スレッドはGoが速かった
XBRL(財務報告の標準形式)の完全な検証エンジンRust下で説明
XBRL から数値を取り出して分析するだけGo下で説明
サーバー・API・AIエージェントから呼ばれる道具Go通信が中心で速度差が出にくく、再ビルドが速い
複数のOSに配る常駐プログラムGo別のOS向けのビルドが簡単で、ファイル1つで配れる
コマンドラインの道具・業務ツールGo書き方が単純で、人が読んで確認しやすい
ブラウザで動かす処理(WebAssembly)Rust出力が小さく、GCを持ち込まない
マイコンRustGCなしで動く。ただし既存の資産はC/C++が最も厚い
データ分析・機械学習どちらでもなく Pythonライブラリの厚みが決め手。重い部分だけRustで補う

XBRL は「どこまでやるか」で分かれる。 数百ファイルに及ぶ定義(タクソノミ)を読み込み、計算式による検証まで行う完全な検証エンジンは、計算が重い。1つのエンジンを、コマンドライン、サーバー、GUI、ブラウザ、他の言語から使い回すことも多い。この使い方には、GCがなく部品として組み込みやすいRustが向く。一方、公開された財務データから数値を取り出して分析するだけなら、中身はダウンロード、読み込み、保存、APIでの提供が中心になる。速度差が表に出にくく、Goの単純さが活きる。

ただし、XBRL の計算式の検証に必要な XPath 2.0 や XML Schema の検証機能は、RustにもGoにも標準ライブラリにはない。本格的な検証エンジンでは、言語の選択より、こうした部品の調達や自作のほうが大きな負担になる。

GUI:画面はWeb技術、裏側の言語は中身で決める

RustにもGoにも、定番と呼べるネイティブのGUI部品は乏しい。実用的な選択肢は、OSに組み込まれたWebブラウザの部品(WebView)で画面を描く、Tauri(Rust)かWails(Go)になる。どちらも画面はHTMLとCSSとJavaScriptで書くので、決め手は裏側の中身である。

条件推奨
中身で重い計算をする、または一般向けに配布するTauri(Rust)。周辺の拡張が厚く、2.0からはiOSとAndroidにも展開できる
中身が薄い社内ツール・個人用ツールWails(Go)。裏側の再ビルドが速く、画面を試しながら作りやすい
1つのOS専用で、見た目も操作感もそのOSの標準にしたいRustでもGoでもなく、macOSならSwift、WindowsならC#(WinUI 3・WPF)

Tauri は、画面の描画に Windows では WebView2、macOS では WKWebView、Linux では WebKitGTK を使う(Tauri の文書)。Wails も同じく、ブラウザを同梱せず各OSの描画部品を使う。

Windows で GUI を作る場合

Windows には、ほかのOSと事情が違う点がある。

  • 画面の描画部品:Tauri も Wails も、Windows では Microsoft の WebView2 を使う。Microsoft の文書によると、WebView2 は Windows 11 には標準で含まれ、Windows 10 でも大多数の環境にすでに入っている。入っていない環境向けには、アプリのインストーラーから導入できる。
  • OSの機能に深く触るならRust:Rust には、Microsoft 自身が保守し、Windows の API(Win32・WinRT・COM)を扱える windows-rs がある。ウィンドウの監視や操作、ショートカットキー、タスクトレイへの常駐など、OSに深く触る道具ではRustが有利になる。
  • 画面のない常駐プログラムならGo:Go には Windows のサービスとして常駐させるための公式の拡張パッケージがあり、開発機とは別のOS向けのビルドも簡単である。
  • 配布の注意:Go の公式FAQは、Goで作った実行ファイルがウイルス対策ソフトに感染と誤判定されることがあり、特に Windows で多いと説明している。一般向けに配るなら、コード署名と事前の確認を前提にしたい。署名がないと Windows の警告が出る点は、言語によらない。

迷ったときの原則

  1. 計算が重く、速さが要件なら Rust。それ以外は Go を基本にする。
  2. GCを持ち込めない場面(ブラウザ、マイコン、他の言語から呼ぶ部品)は Rust。
  3. 1人や少人数なら、1つのプロダクトの中で言語を混ぜない。ビルドもテストも配布も2系統になる。
  4. 人がコードを読んで確認する前提なら、Go に寄せる。
  5. すでに資産がある言語があるなら、それを使う。AIによる保守のしやすさに言語の差は出なかったので、移し替える理由は弱い。

この実験の限界

  • 試行は各1回である。速さの大きな差(2倍以上)は信頼できるが、作業時間の差は誤差の範囲かもしれない。
  • AIは Claude Opus 5.5 の1種類である。同じ Claude でも、別のモデルや別の effort の設定では結果が変わりうる。別の会社のAIでも同じ実験を行ったが、作業の条件をそろえきれなかったため、本稿では扱っていない。
  • お題は計算が中心で、通信や画面を持つプログラムは試していない。用途別の判断のうち、お題以外の部分は実測ではない。
  • 測定機は1台である。CPUが変われば、言語どうしの比も変わりうる。
  • AIは速さを最優先するよう指示されていたため、unsafe の多用やGCの停止のように、普段の開発では選ばない手段も使っている。「速さより安全を優先して」と指示すれば、結果は変わるだろう。

まとめ

AIに「最速で壊れないコード」を書かせるなら、Rust が最も良い結果を出した。手書きのアセンブリは、AIでもコンパイラを超えられず、移植性を失うだけだった。Cは速さでは互角だったが、バグを見つける安全網が薄く、その穴をAIの自作テストで埋めていた。

一方で、AIが保守する限り、言語による保守のしやすさの差はほとんど出なかった。そうなると、言語を選ぶ決め手は2つに絞られる。どこまで速さが要るかと、人間がそのコードを読むかである。速さが要件ならRust、それ以外で人が読むならGo。この2つを問えば、たいていの場面で答えが出る。

キーワード:#perft#Chess960#Wails

出典

コメント