32blogby Studio Mitsu

Rust製CLIはなぜ速い?uutilsの流れも解説

ripgrepやfdがgrepより速い4つの理由をSIMD・ゼロコスト・Rayon・gitignoreで解説。Ubuntu 26.04のuutilsも触れる

by omitsu25 min read
目次

Rust製CLI(ripgrep, fd, bat など)が従来の grepfind より速いのには、具体的な理由が4つある。SIMDによる高速バイト走査(Teddyアルゴリズム)、ゼロコスト抽象化で高級コードがC並みの機械語にコンパイルされること、Rayonによるワークスティーリング並列化でCPU全コアを使い切ること、そして .gitignore を自動で尊重して「検索したくもないファイル」をスキャンしないこと。Linuxカーネルのソースツリー上で ripgrep は GNU grep の約9倍高速、node_modules を抱えた普通の Node.js プロジェクトでは 300倍以上 の差がつくこともある。

モノレポで rg TODO を叩いたら、指がキーボードから離れる前に結果が返ってきた経験はないだろうか。「いや、そんなわけあるか」と思って習慣で grep -r TODO . を叩き、コーヒーを淹れに行って戻ってきてもまだ動いている。この記事に辿り着いた人の多くは同じ疑問を持っているはず。なぜここまで差がつくのか。そして本当に常に Rust 版のほうが速いのか?

結論から言うと「ほぼ常に速い」が正しいのだけど、面白いのはその理由 だ。Rustが検索やファイル走査を魔法で書き換えたわけではない。従来のcoreutilsが対応しようとすれば全面書き換えが必要な設計判断を4つ、具体的に積み重ねただけ。そしてその全面書き換えは今まさに進行中で、uutils/coreutils は Ubuntu 25.10 から標準搭載、Ubuntu 26.04 LTS でも継続採用される。

ツールのインストール方法・エイリアス設定など実践面は Modern Rust CLIツール総論 にまとめてある。この記事はその「なぜ速いのか」を掘り下げる側だ。

Rust製CLIが既存ツールより速い本当の理由

先に誤解を潰しておく。Rust は C より速いわけではない。同じアルゴリズムと同じSIMD命令を使えば、C で書いても同等に速い。そして GNU grep は歴史的に見てかなり丁寧にチューニングされたCコードだ。ripgrep が GNU grep を上回る理由は「Rust が魔法の言語だから」ではなく、ripgrep の作者がより良いアルゴリズムを実装し、Rust がそのアルゴリズムを安全に書きやすくした、というだけの話。

具体的には次の4つ。それぞれ後のセクションで掘り下げる。

  1. SIMDによるバイト走査 — Teddyアルゴリズムと memchr で1サイクルあたり16〜32バイトを一度に比較する
  2. ゼロコスト抽象化 — イテレータやジェネリクスといった高級な書き方が、手書きCと同じ機械語にコンパイルされる。GCも実行時オーバーヘッドもゼロ
  3. Rayonによるワークスティーリング並列化 — ファイル走査と検索が数行のコードで全CPUコアに分散される
  4. .gitignore の自動尊重 — 実プロジェクトには node_modules / target / dist / .venv のような「検索したくないゴミ」が大量にある。これをスキップするのは最適化ではなく、むしろ検索の正しさ に近い

Linuxカーネルのソースツリー でのベンチマークでは、EXPORT_SYMBOL の検索で ripgrep が GNU grep の約 9.2倍spin_lock.*irq のような正規表現で約 11.8倍 速いという結果が出ている。node_modules を含む Node.js プロジェクトでは、デフォルト設定で ripgrep が grep の約 302倍grep に手動で node_modules 除外を指定しても依然として 21倍 速い。

SIMDとTeddyアルゴリズム — 16バイトを一度に走査する

Rust製CLIの速さを支える最大の技術的理由は SIMD(Single Instruction, Multiple Data) だ。現代の x86-64 や ARM CPU は 128bit や 256bit のベクタレジスタを持っていて、1つの命令で16バイトや32バイトを同時に比較できる。従来のC言語の「1バイトずつ読む」ループは1サイクルに1文字しか処理しないが、SIMDコードは16文字を処理する。この差がそのまま速度差になる。

Rust の regex クレート — ripgrep が使っているのと同じ正規表現エンジン — は、Teddy というSIMDアルゴリズムに大きく依存している。Teddy は元々 Geoffrey Langdale が Intel の Hyperscan 正規表現ライブラリ の一部として開発したもので、汎用の正規表現エンジンではなく「リテラル文字列の候補位置を高速に見つけるためのプレフィルタ」。候補がヒットしたときだけ、遅い本体の正規表現エンジンが走る。

実際に何が起きているかというと、ripgrep はほぼメモリ帯域幅に近い速度でファイル全体を流し読みし、「これは怪しい」という場所でだけ立ち止まって精査している。詳細は作者 BurntSushi(Andrew Gallant)のripgrepパフォーマンスに関する解説記事 が圧倒的に読み応えがある。

Teddy の上に、ripgrep は memchr クレート というSIMD加速バイト検索プリミティブも使っている。これは1ループで16バイトを調べる。行数カウント(grep系ツールの隠れたコスト)もパックド比較で16バイトずつ改行を数える。

bash
# Linuxカーネルのクローンで実測比較
hyperfine --warmup 2 \
  'grep -r "EXPORT_SYMBOL" linux/' \
  'rg "EXPORT_SYMBOL" linux/'

hyperfine(これも Rust 製)をインストールして上のコマンドを回すと、CPU やストレージ次第で数字は変わるものの、ripgrep は概ね 5〜15 倍の範囲に収まるはずだ。

なぜ GNU grep は SIMD で書き換えないのか

GNU grep は伝説的なCコードで、実際一部でSIMDも使っているし、単一ファイルのASCII検索では驚くほど速い。ただ数十年分のPOSIX互換性の重みを背負っていて、レガシーなロケールや文字エンコーディングもサポートする必要がある。Teddy 級の全面書き換えはリスクが大きすぎる。ripgrep は2016年にゼロから書き始めた特権を使って、Unicodeデフォルト、UTF-8デフォルト、.gitignoreデフォルトという「現代の開発者のためのgrep」を設計できた。それだけの話。

ゼロコスト抽象化 — 安全性と速度を両立する仕組み

ここで初めてRustに触れる人が一番引っかかる部分を説明する。Rust にはイテレータ、クロージャ、トレイト、ジェネリクス、OptionResult といった、スクリプト言語みたいな高級な書き方が揃っている。なのにコンパイル結果はC並みに速い。なぜ?

答えは モノモーフィゼーション(単態化)LLVM最適化 の組み合わせだ。ジェネリック関数を書くと、コンパイラは実際に使われた具体型ごとに専用のコピーを生成する。動的ディスパッチも仮想関数テーブルの参照もない。機械語になった時点で「抽象」は消え去っていて、残っているのは「最初から手書きしたのと完全に同じコード」だけ。

CLIツール的に言い換えると、こういうこと。

  • iter().filter().map().collect() は手書きCのループと同じ機械語にコンパイルされる
  • Option<T>Result<T, E> はタグ付き共用体として表現され、実行時コストはゼロ。最適化でインライン化されて消える
  • Box<T>Rc<T> は実際に使ったときだけアロケーションが走る。隠れたランタイムはない
  • GC停止時間がない。変数のスコープが終わった瞬間にメモリが解放される

Python、Ruby、Node.js で書かれたツールと比べてみると差が見える。関数呼び出しのたびにVMの境界を越え、オブジェクトは間接参照の層の後ろに座り、GCは数十ミリ秒単位で予測不能に止まる。ack(Perl製)や ag(C製だがI/Oバウンド)のような古い世代のCLIツールが、大きなコーパス上で Rust 製 CLI に追いつけないのはこのせいだ。

Rayonとfearless concurrency — 並列処理を数行で書く

3つ目は一番説明しやすいし、fd やripgrepの再帰検索のようなファイル走査ツールに対するインパクトが一番大きい。

Rayon はRustのライブラリで、通常のイテレータの .iter().par_iter() に変えるだけで並列イテレータになる。内部でワークスティーリング型スレッドプール が動いていて、手が空いたスレッドが忙しいスレッドから仕事を奪う。結果として全コアが遊ばず飽和する。ロックを一行も書く必要がない。

さらに重要なのは、Rust の所有権システムがコンパイル時にデータ競合ゼロ を保証してくれる点。「fearless concurrency(恐れなき並行処理)」はマーケティング用語じゃなくて、ボローチェッカーが文字通り危険な並行コードをコンパイルエラーで拒否する という意味。数百万ファイルを16コアで走査したいCLIツールにとって、これは「髪が抜けるスレッド問題」が「一行差し替えるだけ」に変わることを意味する。

Rust 版 find である fd はこの仕組みで並列ディレクトリ走査を実現している。大規模なディレクトリツリーでの実測では、find -iname 比で 5〜9 倍、find -iregex 比で 9〜23 倍速いというのが典型的な数字。内部で使っているのは walkdir(逐次)や ignore クレート(並列+gitignore対応)で、どちらも ripgrep の作者が管理している。

bash
# fdとfindを実測比較
hyperfine --warmup 2 \
  'find /usr -iname "*.so"' \
  'fd -e so . /usr'

.gitignore を尊重する設計 — 実リポジトリでの圧倒的な差

軽いベンチマークだと「ripgrepは300倍速い」と言われ、本気のベンチマークだと「9倍速い」と言われる。この差はどちらも正しい。単に測っているものが違うだけだ。

現実のリポジトリには機械生成されたコンテンツが信じられないほど大量に含まれている。npm install 一発で node_modules に 200MB の JavaScript が降ってくる。Rust プロジェクトは target/ に何百MBもの .rlib.o を積む。Python の virtualenv は .venv/ に Python 本体ごと隠している。古典的な grep -r はその全部を律儀に検索する。でも、あなたはそれを検索したかっただろうか? まず違う。

ripgrep と fd は .gitignore.ignore.rgignore を自動で読み、マッチしたものを勝手にスキップする。結果として「速く検索している」のではなく「そもそも検索すべきでないデータをそもそも読んでいない」状態になる。典型的な Node.js モノレポで grep -r が 2GB のファイルに触れるところを、rg は 80MB しか触れない。仮にスキャン速度が同じだったとしても、読み込むデータ量の差だけで1桁の差がつく。

node_modules を大量に含むリポジトリでの「302倍」という数字がほぼ不公平に見えるのはこのため。純粋なアルゴリズム比較ではなく、「全部検索する」vs「開発者が本当に検索したいものだけ検索する」の比較になっている。日常業務ではこっちが実感に近い数字だ。

どうしても .gitignore で除外されるファイルを検索したいときは rg --no-ignore または rg -uuu(「制限解除」の3段階)を渡せばいい。デフォルトは意見のあるデザインだが、ロックされているわけではない。設計判断の背景は ripgrep の FAQ に詳しい。作者 Andrew Gallant の根本的な主張は「開発者向けツールは開発者の期待値をデフォルトにすべき」というシンプルなもので、この設計が Reddit や Hacker News でも定番の評価ポイントになっている。

uutilsとUbuntu 26.04 — Rust実装が標準になる流れ

ここまでの話は ripgrep や fd のように「grepfind と並んで使う新しいツール」の話だった。でもその裏で、Rust が古典ツールを静かに置き換えていく もう一つの流れが進行している。

uutils/coreutils プロジェクトは GNU coreutils(ls, cp, mv, cat, date, rm など約100個の基礎コマンド)の Rust 再実装だ。2025年11月、Ubuntu 25.10 が uutils をデフォルトの coreutils 実装として出荷 した。メジャーなLinuxディストリでは初の事例。Ubuntu 26.04 LTS「Resolute Raccoon」はこの選択を継続し、Rust coreutils を標準搭載する初の LTS リリース として 2026年4月23日にローンチ予定だ。

現バージョン(uutils 0.8.0)では GNU coreutils の互換性テストに約 88% 通過している。エンドユーザーから見れば移行は透明で、コマンドの挙動もフラグも出力も変わらない。置き換えの動機は主に性能ではなく(GNU coreutils は既に十分速いCコードだ)、メモリ安全性 にある。バッファオーバーフロー、use-after-free、整数オーバーフローといったバグの大部分が、safe Rust ではそもそも書けない

uutils のリード開発者 Sylvestre Ledru は FOSDEM 2026 でプロジェクトの現状を発表した。そこには正直な数字も含まれていて、sortcp のような一部の特化ツールでは現時点で GNU 版より遅いワークロードがあることも明言している。

Ubuntu 25.10 以降を使っているなら、試しに打ってみると面白い。

bash
# どちらの coreutils が入っているか確認
which ls
ls --version | head -n 1

uutils なら ls (uutils coreutils) 0.8.0(またはディストリのバージョン)、GNU なら ls (GNU coreutils) 9.x と出る。どちらでも動くし、シェルスクリプトは気にしない。

FAQ

Rust は C より速いの?

違う。そしてその質問は問いとして間違っている。同じアルゴリズムを使えば、C でも Rust と同等に速い。Rust 製 CLI が C 製 CLI に勝つ理由は「Rust が魔法だから」ではなく、Rust 版が最近書かれていて最新のアルゴリズム(SIMD、ワークスティーリング並列、.gitignore)を採用している 一方、C 版は数十年前に書かれていて互換性の重みを背負っている から。Rust の本当の貢献は「最新アルゴリズムを安全に書きやすくした」こと。

なら GNU grep を SIMD で書き直せばいい話では?

実際に試みた人はいる。GNU grep も特定ケースでは SIMD を使っている。ただ POSIX 準拠、レガシーロケール対応、巨大なインストールベースの維持という要件があって、Teddy 級の全面書き換えは現実的なリスクが大きすぎる。ripgrep は2016年にゼロから始めた特権を使って、開発者向けワークフローに絞った設計を選べた。

.gitignore 自動スキップで困ることはある?

ある。例えば node_modules の中にうっかりコミットしてしまった秘密情報を探したいとき、ripgrep はデフォルトでは見つけてくれない。--no-ignore(または核オプション -uuu)を明示的に渡す必要がある。このサイレントスキップは意見のあるデザインで、たまに驚く挙動になる。一部のシステム管理者ワークフローが古典的な findgrep を好むのは、まさにこの理由だ。

シェルスクリプトでも grepfind を置き換えるべき?

しないほうがいい。シェルスクリプトでは POSIX 準拠の grep, find, cat を使うべき。どのマシンでも動くし、最小コンテナイメージの CI でも動く。対話的なコマンドラインでの作業では ripgrep, fd, bat を使う。この線引きは Modern Rust CLIツール総論 でも同じ話をしている。速度よりも可搬性が大事な場面のほうが多い。

Ubuntu 26.04 にするとスクリプトが壊れる?

まずない。uutils は GNU 100% 互換を目標にしていて、現時点で約 88% 到達している。実世界のスクリプトがこの差分の 12% に当たる確率は低い。万一レトログレッションに当たったら GNU coreutils をフォールバックパッケージから入れ直せる。既知の非互換は GitHub の issue で追跡されている。

自分で測れるベンチマークは?

hyperfine を入れて、手元にある大きめのコードベースで次を実行してみるといい。

bash
hyperfine --warmup 3 \
  'grep -r "TODO" /path/to/repo' \
  'rg "TODO" /path/to/repo'

--warmup は重要だ。初回実行はコールドキャッシュになって結果が歪む。ファイルベースの CLI をベンチマークするときは最低 2〜3 回のウォームアップを入れること。

ripgrep-allrga)って何?

ripgrep-all は ripgrep をラップして、PDF、電子書籍、Office ドキュメント、SQLite DB の中身まで検索できるようにしたツール。性能プロファイルは別物で、ボトルネックが正規表現エンジンじゃなくドキュメントパーサに移る。便利だけど、普通のソースコード検索で最初に手に取るツールではない。

まとめ

「Rustは速い」というミームは、実際の工学的なストーリーを見えなくしてしまう。Rust 製 CLI が速いのは、作者が SIMD 加速アルゴリズムを選び、Rust が並列処理とゼロコスト抽象化を安全に書きやすくし、そして POSIX の伝統と決別して .gitignore のような現代的なデフォルトを受け入れたからだ。同じ設計判断でCで書き直せば同等の速度は出る。ただ誰もそれをやっていないし、エネルギーが集まっているのはRust側だ。

日常の開発では実践的な結論はシンプル。対話的な作業では ripgrep, fd, bat を使う。シェルスクリプトでは grepfind を残す。そして uutils プロジェクトをこの数年観察する。Ubuntu 28.04 が出る頃には「古典 coreutils」と「Rust coreutils」の境界線は、誰もどちらが入っているか気にしなくなるところまで薄れているはず。

インストールと設定の実践編は Modern Rust CLIツール総論 に、grep から ripgrep への移行パスは grep・ripgrep 深掘り記事 にまとめてある。どのモダンCLIがどの問題を解くのか全体マップが欲しい場合は CLIツールマップ が出発点になる。

短く言い換えるとこういうこと。Rust が物理法則を破ったわけじゃない。ただ「最初からこう書くべきだったコード」を書くコストを、安くしただけだ。