あなたが GC のことをどう思っていようとも、GC はコードを安全にするためにとてつもない恩恵をもたらしました。 オブジェクトが早すぎるタイミングで消えてしまう心配が全く必要ないんです。 (とはいえ、そのオブジェクトへのポインタをその時点まで保有しておくべきかどうかというのは別の問題ですが・・・) これは、C や C++ プログラムが対処しなければならない、広範囲に広がっている問題です。 GC の無い言語を使ったことのあるひとなら誰でも一度はやってしまった、この単純な間違いを見てみましょう。
- fn as_str(data: &u32) -> &str {
- // 文字列を生成する
- let s = format!("{}", data);
- // しまった! この関数内でしか存在しないオブジェクトへの
- // 参照を返してしまった!
- // ダングリングポインタだ! メモリ解放後の参照だ! うわーー!
- // (このコードは Rust ではコンパイルエラーになります)
- &s
- }
これこそが、Rust の所有権システムが解決する問題なのです。 Rust は &s
が生存するスコープを理解し、&s
がそのスコープ外に逃げることを防ぎます。 しかし、この単純なケースは、C コンパイラですらうまいこと防ぐことができるでしょう。 コードが大きくなり、様々な関数にポインタが渡されるようになると、やっかいなことになります。 いずれ C コンパイラは、十分なエスケープ解析ができなくなり、コードが健全である証明に失敗し、屈服することになるのです。 結果的に、C コンパイラはあなたのプログラムが正しいと仮定して、それを受け入れることを強制されます。
これは Rust では決して起こりません。全てが健全であるとコンパイラに証明するのはプログラマの責任なのです。
もちろん、参照が参照先のスコープから逃げ出していないことを検証することよりも 所有権に関する Rust の話はもっともっと複雑です。 ポインタが常に有効であることを証明するのは、もっともっと複雑だからです。 例えばこのコードを見てみましょう。
- let mut data = vec![1, 2, 3];
- // 内部データの参照を取る
- let x = &data[0];
- // しまった! `push` によって `data` の格納先が再割り当てされてしまった。
- // ダングリングポインタだ! メモリ解放後の参照だ! うわーー!
- // (このコードは Rust ではコンパイルエラーになります)
- data.push(4);
- println!("{}", x);
単純なスコープ解析では、このバグは防げません。 data
のライフタイムは十分に長いからです。 問題は、その参照を保持している間に、参照先が変わってしまったことです。 Rust で参照を取ると、参照先とその所有者がフリーズされるのは、こういう理由なのです。
自動で解放されると困ってしまうシナリオがあるからです。
パフォーマンスへの影響
自動解放が仕様になっているプログラミング言語で実装したプログラムは、実行時にGC(ガベージコレクタ)を適宜動かして不要メモリの回収、解放を行います。プログラマが意識することなく勝手に回収してくれて大変便利、素晴らしい!のですが、シビアなタイミングで動作しなければならないプログラムや、CPUパワーを限界まで使いたいプログラムなどでは、このGCが勝手に動くことで致命的な問題を引き起こすことがあります。GCの動作タイミングは原則プログラマが制御できず(明示的に「今GCしろ!」ってのはできる場合もある)、GC実行によってどの程度の負荷がかかるかも予測できません。実装後の計測によってGC動作頻度などを見極めてチューニングする、という苦行を行うぐらいなら、そもそもGCを組み込むプログラミング言語なんか使いたくない、って判断になってしまうこともあります。
管理出来ないメモリ
GCが管理すること自体が問題になってしまうメモリもあります。たとえば他のプロセスがマップしているメモリや、自分自身が明示的にOSから取得したメモリ、通常のRAMではないメモリなどです。このようなメモリは当然プログラマが自分で管理しなければなりません。
GC以外の選択肢
「C言語みたいに全部自分でメモリ管理なんかしたくない、でもGCがある言語も使いたくない」なんて我儘なプログラマのために、昨今はRustのような「GCなしでも誰も使わなくなったメモリは解放するよ」というプログラミング言語も現れています。
その普通って限られた範囲での話なんですよね。
ランタイムに自動開放を任せた場合、アプリケーションの性質によっては余計なタイミングで開放されるとラグとして表面化して動作が不安定になるのです。事務系のアプリならどのタイミングでラグが発生しても気にされないか表面化しないですが、ゲーム等のリアルタイム性が要求されるアプリケーションでやられたらがっかりするユーザーも出てきます。
だからなのか、言語によっては明確に解放を促すメソッドが提供されています。質問者さんからすればこれは犯則行為なんだと思いますが、アプリによっては手動した方が都合がよいケースはあるのです。
実際Javaとかガベージコレクションを実装している言語でゲーム作ってた人の話を聞いていると、随所で無理矢理メモリ解放してたって恨み節含んだ技術エピソードがちらほら出てきます。ランタイムの勝手にさせてたらロクな目に遭わないぞって言います。
リクエストにお応えします。
「メモリの自動解放」は私のすむ世界では普通ではありません。
メモリをソースコードで明示的に管理しなければならない理由は以下です。
- C言語のように言語から意図的にメモリの自動解放機能を外したから
- ヒープを管理するためにリファレンスカウントやマーク&スイープやジェネレーションスキャベンジングなどのGCを実装したら、GCそのものがオーバーヘッドになるから。特にマーク&スイープは、アプリケーションの予期せぬタイミングで実行が一時停止してしまう
- ソースコードでメモリ管理を明示的に書けば、フラグメンテーションの発生を少なく抑えられるから
- ソースコードでメモリ管理を明示的に書けば、メモリの空き領域を増やせるから
- メモリを自動解放する言語でOSを記述すると、OSを動かすためのメタOSが必要になるから
- 1999年に「C言語にGCを組み込む拡張」の論文を書いて情報処理学会のプログラム研究会で発表したのに、査読者の東大助手が却下したから
アセンブラでもCでもいいからRAM MAP作ってグローバル変数として使ってみて下さい。開放は不要だし、そこの変数やフラグは固定だし。ただし使い終わったらクリアするプラグも沢山ありますよ。そうしないと判断できませんから。
なんか世の中高級言語ばっかりで基本のアセンブラ知らないのが多すぎるのは、制御系はめんどくさいと嫌煙してるからなんだろうな。やってみると、メカ、電子回路、全部プログラムで征服できる面白さ判らないんだろうな、と嘆いてみたり
FORTRAN や COBOL には動的にメモリを確保する機能は有りませんでした。ガベージ・コレクションを最初に導入したのは LISP だと聞いています。ガベージ・コレクションを行う言語が普通と考えるのは軽率だと思います。制御器等への組み込みプログラムではコードサイズも考えないといけませんし、予期せぬ割り込みを入れられたくないので、メモリーの自動解放は逆に邪魔です。それにメモリーの自動解放をしてくれる言語でメモリーの事を気にせずに済むのは大きなデータを扱わないからです。巨大なリストを操作する時にはどんな言語でもメモリーの事は気にしてプログラミングをしなくてはなりません。メモリーが足りないとか言われてこけた経験は有りませんか?
普通ねえw まあ最近の言語は大抵そういう機能を持ってますから、無理もないでしょうね。
ただ、他の回答者さん達があまり言及してない点として、一旦起動されると数日以上も終了させられないプログラムがしばしば存在する、ということがあります。
自動で解放されるよね、とタカをくくっていると何重にもメモリの確保を繰り返し、最後には例外で落ちてしまうなんて事故がよく起こります。なので、たとえ自動で解放される言語だからといって確保したものを解放しないと、他のプログラマが大迷惑を被ります。ご用心ご用心。
0 件のコメント:
コメントを投稿