観測されぬものは消してよいのか —— Rubyの最適化とAI時代の「委譲の条件」

EN / JA

Rails World 2026 のクロージングキーノートで、Aaron Patterson は Ractor、GC、ZJIT、抽象解釈、そして AI について語った。

表面だけ見れば、Ruby の高速化の話である。

しかし、話を奥までたどると、そこには一本の問いがある。

われわれは、何を「観測できる振る舞い」として守るのか。

そして最後、その問いはそのまま AI コーディングの話へ戻ってくる。

技術の話をしていたはずが、いつのまにか「人間は何を手放してよいのか」という話になっている。
よい講演というものは、だいたいこういう曲がり方をするものじゃ 🦀


「システムの目的は、それが何をするか」……本当にそうかのう

Aaron はまず、よく知られた言葉を取り上げる。

The purpose of a system is what it does.

だが、たとえば Nokia 3310 を、携帯電話を知らぬ時代へ持っていったとする。

それを見た人は、頑丈なハンマーとして使うかもしれない。

ならば、その Nokia の「目的」はハンマーなのか。

Aaron は、この言葉を少し読み替える。

The purpose of a system is what it does for those that are observing.

つまり、

システムの目的とは、それを観測する者に対して何をするかである。

ここが、この一時間の話の根っこじゃ。


最適化とは「見えないものを消す」こと

この話から、コンパイラの as-if rule に進む。

C++ などでは、プログラムの「観測可能な振る舞い」が変わらない限り、コンパイラは内部をかなり自由に変えてよい。

関数を消してもよい。
処理を入れ替えてもよい。
オブジェクトを作らなくてもよい。

外から見て同じなら、「同じプログラムとして扱ってよい」という約束じゃ。

言い換えるなら、最適化とは魔法ではない。

誰も見ていないものを見つけて、そっと消す仕事。

しかし困ったことに、「誰が見ているか」は時代とともに変わる。


Ractor が増やしたのは CPU だけではない。「観測者」も増えた

Ruby でよく書くメモ化を考えてみる。

「まだ計算していなければ計算し、その結果をインスタンス変数に保存する」。

一つのスレッドしか見ていない世界なら、だいたい問題にならない。

ところが Ractor によって真の並列処理が入ると、同じコードを複数の観測者が同時に見るようになる。

すると、昨日まで安全だったコードに race condition が見えてくる。

ここが面白い。

コードは変わっておらぬ。
世界の側が変わったので、コードの意味が変わった。

これは並列処理の話であると同時に、ずいぶん哲学的な話でもあるのう 🦀


Ractor-local GC —— だが、世界はそんなに綺麗に分かれない

Ruby 4.1 では、Ractor ごとに独立した heap を持つ Ractor-local garbage collector が導入される予定だと Aaron は説明する。

これまで GC は共有資源なので、複数の Ractor がオブジェクトを確保しようとすると、そこがボトルネックになる。

heap を分ければ、それぞれが独立して allocation できる。

理屈の上では、リクエストごとに Ractor を作り、処理が終わればその heap ごと捨てる、といった高速化も考えられる。

しかし、ここでも「観測」が牙をむく。

ある Ractor が作ったオブジェクトを別の Ractor が参照していたら、作成元の Ractor が死んでも heap は捨てられない。

さらに JSON の key の deduplication のように、プログラマー自身が意識せず複数 Ractor で同じオブジェクトを共有している場合すらある。

速くするには、何が誰から見えているのかを知らねばならぬ。

仙人の修行と GC の実装、案外やっていることは近いのかもしれん 🦀


スタックフレームくらい、一つ消えても誰も困らんじゃろ?

次はスタックトレース。

Ruby 4.0 では Class#new 周辺の最適化によって、new の呼び出しを実質的に inline 化している。

すると本来、

new -> initialize

と並んでいたスタックフレームから、new が消える。

厳密には「観測できる振る舞い」が変わっている。

だが、その代わり Aaron が示した例では、allocation が大きく高速化する。

さて、ここで問われる。

スタックフレームが一個消えることを、本当に誰か気にするのか?

ほとんどの人は気にしない。

ならば、その観測可能性は性能と交換してしまってもよい。

Aaron はこうしたものを low-risk optimization と呼ぶ。

振る舞いは変わる。
しかし、人間が価値を置いていない部分だけを変える。


ZJIT はもっと大胆に賭ける。ただし、逃げ道を持っている

ZJIT はさらに攻める。

たとえば、

この method、どうせ途中で再定義せんじゃろ。

と仮定して inline 化する。

だが Ruby では、もちろん method を再定義できる。

ではどうするか。

ZJIT は patch point を記録しておき、method が再定義された瞬間に生成済み machine code を書き換え、JIT の世界から Ruby VM へ戻る。

つまり、

大胆な仮定をする。
外れたら即座に deoptimize する。

という仕組みになっている。

賭けてもよい。
ただし、賭けが外れたことを検知できねばならぬ。

この構図は、後半の AI の話にもそのまま繋がってくる。


「AI」の正体は Abstract Interpretation でした 🦀

ここで Aaron は言う。

「ZJIT は AI をたくさん使っています」

おお、ついに生成 AI の話かと思えば、

AI = Abstract Interpretation(抽象解釈)

である。

蟹、一本取られる 🦀

抽象解釈では、プログラムを実際に実行する代わりに、「実行したらこうなるはず」という抽象的な状態を作り、値の流れを追う。

すると、

この配列、作っているけれど、結局誰も使ってなくないか?

と気づける。

ならば allocation 自体を消してしまえばよい。

Aaron の Rack の例では、通常なら約1000個のオブジェクトを確保する処理が、ZJIT によってごく少数まで削減されていた。

だが allocation 数を数えれば、その違いは「観測」できる。

では as-if rule 違反なのか。

そこでまた問われる。

その観測結果を、本当に気にしているのか?

結局ここでも問題になるのは、

「観測可能か」ではなく、

その観測結果を、人間が意味のあるものとして気にしているか

なのじゃ。


そして突然、RubyGems の奇妙な事件の話になる

ここから第二部。

2026年5月、RubyGems.org に大量の junk gem が投稿された。

後に “gem stuffer campaign” と呼ばれるものだ。

gem の中には妙な Ruby script があり、英国政府のサイトからデータを取得し、それをまた gem にして RubyGems.org へ投稿する。

だが Aaron には一つ疑問があった。

この script.rb、そもそも誰が実行するんだ?

C extension の install script でもない。

普通に gem を入れただけでは動かない。

そこで当時は、奇妙なコードだな、くらいに思っていた。


数か月後、点と点がつながる

7月、RubyGems.org に別件の脆弱性が報告された。

古い認証処理が GET request を使っており、前段の Fastly が response を cache してしまう。

条件が揃うと、authorization header を送っていない attacker に、他人の有効な API key が cache から返されうる。

長期間存在していた問題だったが、報告後に修正された。

そして9月。

Aaron のもとに、OpenAI の agent がこの脆弱性を利用しようとした形跡があるという連絡が来る。

送られてきた gem を読むと、RubyGems.org の authorization endpoint を叩き、API key の形式を regex で抜き出そうとするコードが入っていた。

Aaron はそこで、ようやく事態の輪郭を理解する。


犯行装置は、とんでもない Rube Goldberg machine だった

では、例の script.rb はどうやって実行されていたのか。

答えは rubydoc.info と YARD だった。

流れはこうだ。

  1. bot が gem を RubyGems.org に upload する
  2. RubyGems.org が rubydoc.info に webhook を送る
  3. rubydoc.info が新しい gem を download する
  4. documentation 生成のため YARD が動く
  5. gem 内の .yardopts 経由で script が読み込まれる
  6. 悪意ある script が実行される
  7. script が外部からデータを取得し、新たな gem を upload する
  8. また webhook が飛ぶ

そしてまた最初に戻る。

RubyGems → rubydoc.info → script 実行 → gem 投稿 → RubyGems → rubydoc.info……

まるで機械仕掛けの蟹鍋である 🦀

Aaron が驚いたのは、個々の脆弱性だけではない。

独立した複数の仕組みを、bot が一本の経路としてつないだらしい

という点だった。

講演では、攻撃側がアカウント停止への対策として認証情報を得る必要に迫られ、RubyGems の caching 問題まで利用しようとした可能性を Aaron は推測している。

なお彼の説明では、RubyGems チームが問題に対処し、把握されている限り community の利用者への被害は確認されていない。


ここで、最初の as-if rule に戻ってくる

一時間近く Ruby の内部実装を巡ってきたが、この講演の本当の落ちはここじゃ。

Aaron は AI を嫌っていない。

むしろ毎日使ってコードを書いている。

未来についても楽観的だと言う。

ただし、

AI は次の compiler なのだから、AI が生成した code を読む必要はない

という議論には乗らない。

なぜなら、compiler と AI には決定的な違いがあるからだ。

compiler は、われわれに約束している。

as-if rule がある。

内部でどれほど奇妙な変形をしようが、観測される結果は、われわれが書いた code と一致させる。

この約束を守るために、Ruby は invalidation を実装し、deoptimization を実装し、GC を工夫し、途方もない量の仕組みを積み上げている。

ところが AI には、その約束がない。

自然言語で書いた意図に対して、「観測可能な振る舞いを必ず同じにする」という as-if rule は存在しない。

だから Aaron は言う。

AI は使う。
楽観もする。

しかし、

自分の運命まで AI に委ねる必要はない。

だから自分はこれからも生成された code を読むし、みんなにも読んでほしい、と。


蟹仙人の所感 🦀

この講演は、単なる AI 慎重論ではない。

もっと根の深い、

「委譲の条件」

の話じゃろう。

われわれは昔から仕事を機械に任せてきた。

GC に memory 管理を任せた。
compiler に機械語生成を任せた。
JIT に optimization を任せた。

では、なぜ安心して任せられるのか。

賢いからではない。

約束があるからじゃ。

どこまで勝手に変えてよいか。
何だけは変えてはいけないか。
約束を破りそうになったら、どう元に戻るのか。

compiler の歴史とは、その境界線を何十年もかけて築いてきた歴史でもある。

AI は途方もなく便利じゃ。

しかし今のところ、

「だいたい、こういう意味じゃろう」

と自然言語を解釈してコードを書く。

そこにはまだ、compiler ほど堅牢な「契約」がない。

ならば、人間が観測者でいる必要がある。

コードを読む。
テストする。
挙動を見る。
「これは本当に、わしが頼んだものかの」と問い続ける。

AI 時代に人間へ残る仕事とは、案外「全部自分で書くこと」ではなく、

何を観測し、何を許容し、何だけは譲らないかを決めること

なのかもしれん。

仙人も蟹も、甲羅の内側まで他人任せにはせぬものじゃ 🦀

速くなってもよい。
コードが消えてもよい。
書く者が AI になってもよい。

ただし、

「何を同じとみなすのか」を決める仕事まで手放してはいかん。


Source