RAII、Rust/Linuxの幻想
(kristoff.it)要約
Rust開発者と既存のLinux開発者のあいだの対立を見ていて書いた文章です。開発者ごとに異なるコーディングスタイルを持つことはありえますが、LinuxプロジェクトはすでにC++を排除し、そのコードスタイルや構造(RAII)を避けてきた前例があります。
Asahi Linaが言及したコードの動作方式は、そのプログラムを終了するときに遅すぎるうえ、性能志向のソフトウェアを作るうえで最も基本的な方法であるバッチ処理と相反します。たとえば、メモリ領域を使ってバッチ処理を行えば、複数のライフタイムをひとつにまとめて管理できるため、RAIIは必要ありません。
ここでは、私の主張を裏づける資料を示します。これらの資料はいずれも、なぜバッチ処理が優れているのかを説明しています。
- Casey Muratori | Smart-Pointers, RAII, ZII? Becoming an N+2 programmer
- CppCon 2014: Mike Acton "Data-Oriented Design and C++"
- Modern Systems Programming: Rust and Zig - Aleksey Kladov
したがって、私はLinuxが今後もRAIIを受け入れるべきではないと考えます。
私がこの文章を持ってきた理由はこうです。韓国のRust開発者たちがあの記事を見てとても怒っている様子を何度も見かけたので、ここの皆さんはどう考えたのか気になったからです。皆さんはどう思いますか。
11件のコメント
私見ですが、特定の開発者たちのエリート主義もある程度は理解できます。ソフトウェア「工学」の観点から見ると、特にLinuxのように、今日のオープンソース陣営においてクローズド陣営とも幅広く手を組み、オープンソース哲学の進歩に貢献してきた「ソフトウェア」はなかなか見当たりません。そのため、実績のないプログラマーたちがRustを掲げて雪崩れ込んできて、既存プロジェクトの保守中核の統制を外れたコードを次々と継ぎ足し、技術的負債をむやみに増やしてLinuxのライフサイクルを縮めてしまうのではないかと懸念し、より排他的で、まるでラッダイトのようにさえ見える保守的な態度を取り続けているのではないでしょうか。
オープンソースが長くオープンソースであり続けるために、「オープン」ではない態度を取るのは興味深いです。
私も RAII やそれに類する形式のリソース管理をよく使いますし、推奨することが多いです。RAII が何なのか分かっていなくて、深く考えずに使っていても、「ひとまず安全な」コードになりやすいからです。
ただし、きちんと理解せずに使うと、ファイルを一度だけ開けば済むのに何十回も開いて閉じるような、非効率なコードが量産されがちです。開発者が継続的に性能へ関心を持っていて、そうした文化が開発チームの前提になっているなら、RAII でも十分な水準の性能を出せると思います。
Linux で 2 を 1 より高速に実行できるようにする機能? API のようなものはありますか?
私は当然 1 でやってきたので、いまいち理解できなくて。
開発を全部終えたあとに、valgrindでメモリリークを探すような開発体験にまた戻りたくはないですね。
正確には分かりませんが、RAIIを使わないというのは、意図的なメモリリークを利用して(クローズの)性能を引き上げようということのように見えるのですが、これが正しい方向性なのかはよく分かりません。
どうせ手動でメモリ管理をうまくできる開発者ならRAIIも上手に使うでしょうし、RAIIなしでは開発できない開発者は手動でのメモリ管理もできないでしょうから、RAIIを使わない理由はないように思います。
freeがどれくらい時間を消費するのか気になって、実際のワークロードとはかなり異なりますが、簡単にコードを書いてテストしてみました。(Rust のリリースビルドで、std::alloc::allocとstd::fs::Fileを使用しました。)さまざまなサイズのメモリを 10,000,000 個、約 2.5GB 分あらかじめ割り当てておき、解放にかかる時間だけを測定したところ、1.87 秒かかりました。1 個あたり 187ns です。
一方、ファイルの場合は、ハンドルを 10,000 個ほど開いておき、閉じるのにかかる時間だけを測定したところ、約 9 秒でした。ファイル 1 個あたり 900us かかった計算です。
(この Windows PC はアンチウイルスのせいなのかファイル操作が妙に遅いのですが、別の Windows ノート PC ではそれぞれ 400ns/200us、別の Linux PC では 50ns/600ns でした。)
RAII の代替として、バルク処理や、終了時は OS を信じてリソースをリークさせる方法がよく挙げられますが、メモリなら簡単に可能でしょう。
しかし、ファイルやソケットのようなリソースでは、バルク回収 API は見たことがありませんし、リソースをリークさせればユーザーコード側の時間は減るかもしれませんが、その減った分だけそっくりそのままカーネルがプロセスを終了させるのにかかる時間が増えるので、性能上の利益はあまりありません。
メモリ RAII は比較的そこまで遅いわけでもなく、arena の利用を不可能にする技術でもなく、必要なら意図的なリークをできなくするものでもないので、RAII を忌避する理由にはなりにくいように思います。
そして、より遅いファイル RAII の場合は、バルクで処理する方法もなく、コストを避ける方法もない状況で、RAII の代替がどれほど優れているのか気になります。
少し本題から外れますが、RAII と lifetime に関する反論は、
malloc/freeに代表されるメモリ資源にだけ話を限定して議論されている、という印象を受けます。RAII と lifetime はメモリ割り当てだけでなく、ファイル、ソケット、ロックといった OS 資源はもちろん、オブジェクトプール、コネクションプールなどのように、
取得と返却があり、取得中は排他的アクセス制御が必要な、ほとんどのリソースのモデリングに広く有用です。
こうしたリソースも
malloc/freeと同じような構造を共有しているため、リーク、use after free、double free といった同型の問題を共有しており、同じ構造を共有しているおかげで、RAII と lifetime がメモリだけでなく、こうしたリソースの問題までまとめて解決するという点は、もっと注目される必要があると思います。
たとえば Rust では、ファイルハンドルについても use after close や double close をコンパイル時に防げます:
https://play.rust-lang.org/?version=stable&mode=debug&edition=…
主要な GC 言語はメモリを GC で管理していても、結局のところファイルハンドルやソケットのような、管理が決定論的である必要のあるリソースのために、
RAII のような仕組みや(Java の try-with-resources statement、C# の using statement、Python の with statement など)、似た仕組み(Go の
deferなど)を追加で導入して、最終的には 1 つの言語に複数のリソース管理モードを持つことになりますが、そうした形よりは少し良いのではないかと思います。
これがアリーナを意味しているのであれば、Rustにも当然アリーナがあり、lifetimeによってアリーナをなくした後に「一括解放」してからアリーナの要素にアクセスすることを禁止するのも可能です。https://crates.io/keywords/arena をご参照ください。
zig や rust の後にも、多くの言語が出てきてほしいと思っています。でも、今のところ rust ほど適切な言語は見たことがありません。むしろ、こうした言語間の議論の中から生まれる開発者同士の知見のほうが有用だと思います。はは..
私も一応 Rust を主力言語として使っている開発者ですが、腹は立たなかったものの、やや極端な例(「そのプログラムを終了するときにあまりにも遅い」として、リンク先の動画でも Rust プロジェクトでの事例と直接の関係はない、Visual Studio を終了する際に各個別コンポーネントのデストラクタが呼ばれることで時間がかかりすぎる、という例を挙げています)を持ち出してきているのではないか、とは思いました。
性能上、複数コンポーネントのクリーンアップを一度に処理する必要がある場合には、個別コンポーネントごとに
Dropを実装する代わりに、それらのコンポーネントのライフタイムを保持している型側でクリーンアップをまとめて実行するようDropを実装する、という方法を取れるのではないかと思います。そのコンポーネントをその型の API を通してのみ生成できるような安全策を設ければ、なおよいでしょう。もちろん、元記事の著者が懸念しているのは、RAII を使う慣行が Linux のコードベースに入ってきた場合、巨大なコードベースの複雑さの中で、かなり暗黙的な性能上の懸念があるコードが蓄積し、長期的には Visual Studio と似たようなことが起こりうる、という点なのだと思います。これは十分に懸念に値する点だと思います。ただ、他のコメントで言われているように、RAII が提供する安全性もあるので、選択はある程度トレードオフだと見ています。
どちらも正しいことを言っています。
たとえて言えば、オンラインゲーム『LoL』のチャンピオンであるアジールは、スプリット運用や集団戦でのエリア支配力、そしてアルティメットの価値が圧倒的に高いハイティアチャンプだという認識がありますが、これは高度に熟練したプロの試合でこそ通用する話で、一般人レベルではレーニングも弱すぎるうえに素の強さも低く、ただの最下位ティアのチャンピオンにすぎません。
アサヒ・リナのように、上位10%を超えるプログラミングおよびオペレーティングシステムの知識を持つ人たちの立場からすれば、RAII以外の案のほうが当然よいのでしょうが、残り90%が扱う領域では、RAIIやRustに勝るものはないと思います。
ただ、メモリの安定性・安全性を保証しなければならない大きな理由のひとつにセキュリティ問題があるので……トレードオフは避けられないと思います。
RAII がないと、経験が比較的浅い開発者はバグを量産してしまいそう。
OS ではなくアプリケーションレベルでは、少なくとも…