web-dev-qa-db-ja.com

ガベージコレクションは、ネイティブにコンパイルされた言語でどのように機能しますか?

スタックオーバーフローのいくつかの回答を参照した後、 一部のネイティブにコンパイルされた言語にはガベージコレクションがあります であることは明らかです。しかし、これがどのように機能するのか正確にはわかりません。

ガベージコレクションがインタープリタ型言語でどのように機能するかを理解しています。ガベージコレクターは、単にインタープリターと一緒に実行され、プログラムのメモリから未使用で到達できないオブジェクトを削除します。彼らは両方一緒に実行されています。

しかし、これはコンパイルされた言語でどのように機能しますか?私の理解では、コンパイラがソースコードをターゲットコードにコンパイルしたら具体的にはネイティブマシンコードです-これで完了です。その仕事は終わりました。では、コンパイルされたプログラムをどのようにしてガベージコレクションするのでしょうか。

プログラムが「ゴミ」オブジェクトを削除するために実行されている間、コンパイラは何らかの方法でCPUと連携しますか?または、コンパイラは、コンパイルされたプログラムの実行可能ファイルに最小限のガベージコレクタを含めますか?.

スタックオーバーフローに関するこの回答 からのこの抜粋により、私の後者のステートメントは前者よりも有効であると思います。

そのようなプログラミング言語の1つがエッフェルです。ほとんどのEiffelコンパイラは、移植性の理由でCコードを生成します。このCコードは、標準のCコンパイラによってマシンコードを生成するために使用されます。 Eiffel実装は、このコンパイルされたコードにGC(場合によっては正確なGC)を提供し、VMは必要ありません。 特に、VisualEiffelコンパイラは、GCを完全にサポートするネイティブx86マシンコードを直接生成しました

最後のステートメントは、コンパイラーがプログラムの実行中にガベージコレクターとして機能する最終的な実行可能ファイルにいくつかのプログラムを含めることを意味するようです。

ガベージコレクションに関するD言語のWebサイト -のページは、ネイティブにコンパイルされ、オプションのガベージコレクターを備えています-ガベージコレクションを実装するために、元の実行可能プログラムと一緒にバックグラウンドプログラムが実行されることを示唆しているようです。

Dは、ガベージコレクションをサポートするシステムプログラミング言語です。通常、明示的にメモリを解放する必要はありません。必要に応じて割り当てるだけで、ガベージコレクターは定期的にすべての未使用メモリを利用可能なメモリのプールに返します

上記の方法が使用されている場合、それはどのように正確に機能しますか?コンパイラーはガベージコレクションプログラムのコピーを保存し、生成した各実行可能ファイルに貼り付けますか?

または私は私の考えに欠陥がありますか?もしそうなら、コンパイルされた言語のガベージコレクションを実装するためにどのような方法が使用され、それらはどのように正確に機能しますか?

80
Christian Dean

コンパイルされた言語のガベージコレクションは、インタープリター型言語の場合と同じように機能します。 Goなどの言語では、通常、コードが事前にマシンコードにコンパイルされているにもかかわらず、ガベージコレクターをトレースします。

(トレース)ガベージコレクションは、通常、現在実行中のすべてのスレッドの呼び出しスタックをウォークすることから始まります。それらのスタック上のオブジェクトは常にライブです。その後、ガベージコレクターは、ライブオブジェクトグラフ全体が見つかるまで、ライブオブジェクトによってポイントされているすべてのオブジェクトを走査します。

これを行うには、Cなどの言語が提供しない追加情報が必要であることは明らかです。特に、すべてのポインター(およびおそらくそれらのデータ型)のオフセットを含む各関数のスタックフレームのマップと、同じ情報を含むすべてのオブジェクトレイアウトのマップが必要です。

ただし、強力な型保証がある言語(たとえば、異なるデータ型へのポインターキャストが許可されていない場合)は、コンパイル時にこれらのマップを実際に計算できることが簡単にわかります。命令アドレスとスタックフレームマップ間の関連付け、およびバイナリ内のデータ型とオブジェクトレイアウトマップ間の関連付けを格納するだけです。この情報により、オブジェクトグラフのトラバースが可能になります。

ガベージコレクター自体は、C標準ライブラリーと同様に、プログラムにリンクされたライブラリーにすぎません。たとえば、このライブラリは、メモリ負荷が高い場合に収集アルゴリズムを実行するmalloc()に類似した関数を提供できます。

54
avdgrinten

コンパイラはガベージコレクションプログラムのコピーを保存し、生成する各実行可能ファイルに貼り付けますか?

それは不格好で奇妙に聞こえますが、はい。コンパイラーには、ガーベッジ・コレクション・コードだけでなく、それ以上のものを含むユーティリティー・ライブラリー全体があり、このライブラリーへの呼び出しは、作成する各実行可能ファイルに挿入されます。これは ランタイムライブラリ と呼ばれ、通常はどれだけ多くの異なるタスクが機能するかに驚くでしょう。

122
Kilian Foth

または、コンパイラはコンパイルされたプログラムのコードに最小限のガベージコレクタを含めますか?.

それは「コンパイラがプログラムをガベージコレクションを実行するライブラリにリンクする」という奇妙な言い方です。しかし、そうです、それが起こっているのです。

これは特別なことではありません。コンパイラは通常、コンパイルするプログラムにライブラリのtonsをリンクします。さもなければ、コンパイルされたプログラムは、多くのことをゼロから再実装することなしには、あまり多くを行うことができませんでした。

しかし、おそらくGCは、ユーザーが呼び出す明示的なAPIを提供するこれらの他のライブラリとは異なりますか?

いいえ:ほとんどの言語では、ランタイムライブラリは、GCを超えて、公開APIなしで多くの舞台裏の作業を行います。次の3つの例を検討してください。

  1. 例外の伝播とスタックの巻き戻し/デストラクタの呼び出し。
  2. 動的メモリ割り当て(ガベージコレクションがない場合でも、通常はCのように関数を呼び出すだけではありません)。
  3. 動的型情報の追跡(キャストなど)。

したがって、ガベージコレクションライブラリはまったく特別なものではありません。prioriは、プログラムが事前にコンパイルされたかどうかには関係ありません。

58
Konrad Rudolph

しかし、これはコンパイルされた言語でどのように機能しますか?

あなたの言い回しは間違っています。 プログラミング言語 は、いくつかのテクニカルレポートに記述された仕様です(良い例については、 R5RS を参照してください)。実際には、いくつかの特定の言語実装(これはソフトウェアです)を参照しています。

(一部のプログラミング言語は仕様が悪い、または欠落している、またはいくつかのサンプル実装に準拠している;それでも、プログラミング言語は動作を定義する-例えば 構文 および セマンティクス -、それはではないソフトウェア製品ですが、実装されている可能性があります一部のソフトウェア製品による;多くのプログラミング言語にはいくつかの実装があります;特に、「コンパイル済み」は実装に適用される形容詞です-一部のプログラミング言語は、コンパイラーよりもインタープリターの方が実装しやすい場合でも)

私の理解では、コンパイラーがソースコードをターゲットコード(具体的にはネイティブマシンコード)にコンパイルすると、コンパイルが完了します。その仕事は終わりました。

インタプリタとコンパイラの意味はばらばらであり、一部の言語実装は両方とも考えられることに注意してください。つまり、その間に連続体があります。最新の ドラゴンブック を読み、 bytecodeJITコンパイル動的にCコードを出力することを考えてくださいそれはいくつかの「プラグイン」にコンパイルされ、次に dlopen(3) -edが同じプロセスで行われます(現在のマシンでは、これは対話型と互換性があるほど高速です [〜#〜 ] repl [〜#〜] 、参照 this


GCハンドブック を読むことを強くお勧めします。 回答するには本全体が必要です。その前に、 ガベージコレクション ウィキページ(以下を読む前に読んだと思います)を読んでください。

コンパイルされた言語実装の ランタイムシステム にはガベージコレクターが含まれており、コンパイラーはその特定のランタイムシステムにfitであるコードを生成しています。特に、割り当てプリミティブ(マシンコードにコンパイルされる)は、ランタイムシステムを呼び出します(または呼び出します)。

では、コンパイルされたプログラムはどのようにしてガベージコレクションされるのでしょうか。

ランタイムシステムを使用する(そして「フレンドリー」で「互換性がある」)マシンコードを発行するだけです。

いくつかのガベージコレクションライブラリ、特に Boehm GCRavenbrookのMPS 、または私の(メンテナンスされていない) Qish を見つけることができることに注意してください。そして、simpleGCのコーディングはそれほど難しくありません(ただし、デバッグはより難しく、competitiveGCのコーディングは難しい)。

場合によっては、コンパイラーはconservativeGC( Boehm GC など)を使用します。その後、コーディングすることはほとんどありません。保守的なGCは(コンパイラがその割り当てルーチンまたはGCルーチン全体を呼び出す場合)時々scancall stack 全体をスキャンし、すべてのメモリが呼び出しスタックから到達可能な(間接的に)ゾーンはライブです。タイプ情報が失われるため、これはconservativeGCと呼ばれます。コールスタックの整数がたまたまアドレスのように見えると、それが追跡されます。

他の(より難しい)ケースでは、ランタイムは 世代別コピーガベージコレクション を提供します(典型的な例はOcamlコンパイラーで、OcamlコードをマシンにコンパイルしますこのようなGCを使用したコード)。次に問題は、呼び出しで正確にを見つけてすべてのポインタをスタックすることであり、それらの一部はGCによって移動されます。次に、コンパイラは、ランタイムが使用するコールスタックフレームを説明するメタデータを生成します。つまり、 呼び出し規約[〜#〜] abi [〜#〜] は、その実装に固有の固有になります(つまり、コンパイラ)とランタイムシステム。

場合によっては、コンパイラによって生成されたマシンコード(実際には closures それを指している)自体がガベージコレクションされます。これは特に [〜#〜] sbcl [〜#〜] (良いCommon LISP実装)の場合で、すべての [〜#〜] repl [〜# 〜] 相互作用。これには、コードとその内部で使用される呼び出しフレームを記述するいくつかのメタデータも必要です。

コンパイラーはガベージコレクションプログラムのコピーを保存し、生成した各実行可能ファイルに貼り付けますか?

並べ替え。ただし、ランタイムシステムは共有ライブラリなどになる可能性があります。(Linuxや他のいくつかのPOSIXシステムでは)場合によっては、スクリプトインタープリターなどになることもあります。 execve(2)Shebang とともに渡されます。または [〜#〜] elf [〜#〜] インタープリター、 elf(5) およびPT_INTERPなどを参照してください。

ところで、ガベージコレクション(およびそのランタイムシステム)を備えた言語のほとんどのコンパイラは、現在 フリーソフトウェア です。ソースコードをダウンロードして調べてください。

23

すでに良い答えはいくつかありますが、この質問の背後にあるいくつかの誤解を解消したいと思います。

それ自体、「自然にコンパイルされた言語」のようなものはありません。たとえば、同じJava=コードは、古い電話(Java Dalvik)で解釈され(そして部分的に実行時にコンパイルされます)、(前もって)コンパイルされます私の新しい電話(ART)。

コードをネイティブで実行することと解釈されることの違いは、見た目ほど厳密ではありません。どちらも、実行するためにいくつかのランタイムライブラリとオペレーティングシステムを必要とします(*)。解釈されたコードにはインタープリターが必要ですが、インタープリターはランタイムの一部にすぎません。ただし、インタプリタを(ジャストインタイム)コンパイラで置き換えることができるため、これは厳密ではありません。最大のパフォーマンスを得るには、両方が必要になる場合があります(デスクトップJavaランタイムには、インタープリターと2つのコンパイラーが含まれています)。

コードをどのように実行しても、同じように動作するはずです。メモリの割り当てと解放は、ランタイムのタスクです(ファイルを開く、スレッドを開始するなど)。あなたの言語では、単にnew X()などを書くだけです。言語仕様は何をすべきかを示し、ランタイムはそれを行います。

いくつかの空きメモリが割り当てられ、コンストラクタが呼び出されます。十分なメモリがない場合、ガベージコレクタが呼び出されます。ネイティブコードであるランタイムに既にいるので、インタープリターの存在はまったく問題ではありません。

コードの解釈とガベージコレクションの間には、直接の関係はありません。 Cのような低水準言語は、すべてを高速かつきめ細かく制御できるように設計されているだけであり、非ネイティブコードやガベージコレクターのアイデアにうまく適合しません。したがって、相関関係があります。

これは、昔は非常に真実でした。 Javaインタープリターは非常に遅く、ガベージコレクターはかなり非効率的でした。現在、状況は大きく異なり、インタープリター言語について話すことは意味を失いました。


(*)少なくとも汎用コードについて話すときは、ブートローダーなどを除外します。

6
maaartinus

詳細は実装によって異なりますが、一般的には以下のいくつかの組み合わせです。

  • GCを含むランタイムライブラリ。これはメモリ割り当てを処理し、「GC_now」関数を含む他のいくつかのエントリポイントがあります。
  • コンパイラーは、GCのテーブルを作成して、どのデータ型のどのフィールドが参照であるかを認識できるようにします。これは、GCがスタックからトレースできるように、各関数のスタックフレームに対しても行われます。
  • GCが増分(GCアクティビティがプログラムとインターリーブされる)または同時(別のスレッドで実行)の場合、コンパイラーは、参照が更新されたときにGCデータ構造を更新するための特別なオブジェクトコードも含めます。この2つには、データの整合性に関して同様の問題があります。

インクリメンタルおよびコンカレントGCでは、コンパイルされたコードとGCが連携して、いくつかの不変条件を維持する必要があります。たとえば、コピーコレクタでは、GCはスペースAからスペースBにライブデータをコピーし、ゴミを残します。次のサイクルでは、AとBを入れ替えて繰り返します。したがって、1つのルールは、ユーザープログラムがスペースAのオブジェクトを参照しようとするたびにこれが検出され、オブジェクトがスペースBに直ちにコピーされ、プログラムが引き続きアクセスできるようにすることです。転送アドレスはスペースAに残され、GCにこれが発生したことを示します。これにより、オブジェクトへの他の参照がトレースされるときに更新されます。これは「読み取りバリア」として知られています。

GCアルゴリズムは60年代から研究されており、この主題に関する広範な文献があります。詳細については、Googleをご覧ください。

3
Paul Johnson