Fedora 26
を実行しています。
これは、アルゴリズムの教授から与えられた非常に奇妙な割り当てです。割り当ては言う:
Cでのメモリの断片化:
以下を実行するCプログラムを設計、実装、および実行します。3m
配列のシーケンスにメモリを割り当てます。各サイズは800,000要素です。次に、すべての偶数番号の配列の割り当てを明示的に解除し、それぞれ900,000要素のサイズのm
配列のシーケンスを割り当てます。プログラムが最初のシーケンスの割り当てと2番目のシーケンスの割り当てに必要な時間を測定します。m
を選択して、プログラムで使用できるほとんどすべてのメインメモリを使い果たします。 "
これの全体的な目標は、メモリを断片化してから、連続したチャンクとして利用できる量よりもわずかに多くを要求し、オペレーティングシステムにメモリの最適化または最適化を強制することです。
クラスでは、メモリが視覚化されており、実際には隣接していないため、これをどのように行うべきかを尋ねました。他の何人かの学生は、クラスでこの「ガベージコレクション」にいつ到達したかを知る必要があると尋ね、彼は次のように述べました。
少し調べた後、仮想メモリを無効にするのに最も近いのは、swapoff -a
でスワップメモリを無効にすることでした。デスクトップ環境を無効にして、ネイティブターミナルからプログラムをコンパイルして実行しました(他のプロセス、特にデスクトップ環境のような重いプロセスからの干渉を避けるため)。これを行い、2番目の割り当てのタイミングが最初の割り当てよりも大きくなるポイントに到達するまで、m
を増やしながらプログラムを実行しました。
m
を増やしながらプログラムを実行したところ、2番目の割り当ての時間が最初の割り当ての時間よりも長くなるポイントが見つかりました。しかし途中で、2番目の割り当ての前にプロセスが強制終了されるポイントに到達しました。 dmesg
を確認したところ、oom
- killerによって殺されたことがわかりました。 oom
- killerに関するいくつかの記事を見つけて読み、カーネルによるメモリの過剰割り当てを無効にできることを発見しました。
私はこれを実行してプログラムを再度実行しましたが、今回のみ、2番目のタイミングが最初のタイミングよりも高いm
を見つけることができませんでした。結局、mを大きくすると(割り当て超過が有効になっている場合よりもはるかに小さくなりますが)、mallocが失敗し、プログラムが終了します。
3つの質問があります。最初の質問はそれほど重要ではありません。
ガベージコレクションはこれの正しい用語ですか?私の教授は、これはガベージコレクションであると言っていますが、ガベージコレクションはプログラミング言語によって行われるものであり、これはより最適化されていると考えられていると思い込んでいました。
Linuxシステムで彼が望んでいるような圧縮は可能ですか?
スワップを無効にしてもメモリの割り当て超過が有効になっているときに、2番目の割り当ての時間が最初の割り当てよりも長くなるポイントに到達できたのはなぜですか?圧縮は実際に行われましたか?もしそうなら、メモリの割り当て超過を無効にした後、圧縮が発生するポイントに到達できなかったのはなぜですか?
これまでの研究に対する称賛です。これは確かに興味深い一連の質問です。
ここで一般的に考慮すべき重要な側面があります。メモリ割り当ては、一部はオペレーティングシステムの責任であり、一部は実行中の各プロセスの責任です(メモリ保護と仮想アドレス空間のない古いシステムは無視します)。オペレーティングシステムは、各プロセスに独自のアドレススペースを提供し、必要に応じて物理メモリをプロセスに割り当てます。各プロセスは、アドレス空間を(ある程度)分割し、適切に使用されるようにします。ランタイム環境がほとんどのことを処理するので、プロセスの責任はプログラマーにはほとんど見えないことに注意してください。
今、あなたの質問に答えるために...
私の考えでは、ガベージコレクションは、ここで行っていることから1ステップ削除されています。 malloc()
とfree()
を使用してCで記述していると思います。 ガベージコレクション 、プログラミング言語およびランタイム環境でサポートされている場合、後者の部分を処理します。メモリのブロックを識別しますこれらは以前に割り当てられていたが、現在は使用されておらず(そして重要なことに、再び使用することはできません)、それらをアロケータに返します。 質問はリンクされていますJdeBP の コメント はいくつかの背景を提供しますが、人によって意見が大きく異なることを示しているため、ほとんど興味深いと思いますガベージコレクション、さらにはガベージコレクションを構成するものです。
私たちが関心を持っている状況では、「メモリ圧縮」を使用して、議論中のプロセスについて話します。
ユーザー空間プログラミングの観点から見ると、Linuxでは、Cで教授が求めていることは不可能です。ここで私たちが気にしているのは、物理メモリの断片化ではなく、アドレス空間の断片化です。 800,000バイトのブロックを多数割り当てると、各ブロックへのポインタと同じ数のポインタが作成されます。 Linuxでは、この時点ではオペレーティングシステム自体はあまり機能しておらず、各割り当てをサポートする物理メモリは必ずしも必要ではありません(余談ですが、割り当てが小さければ、オペレーティングシステムはまったく関与しません。 Cライブラリのアロケータ;ただし、ここでの割り当ては、Cライブラリがカーネルによって処理されるmmap
を使用するのに十分な大きさです。奇数番号のブロックを解放すると、アドレス空間のそれらのブロックが返されますが、他のブロックへのポインターを変更することはできません。ポインターを移動しながら出力すると、それらの違いは割り当て要求(私のシステムでは802,816バイト)以下であることがわかります。 900,000バイトブロックの2つのポインタの間にスペースはありません。プログラムには、より抽象的な値(他のコンテキストではハンドル)ではなく、各ブロックへの実際のポインターがあるため、ランタイム環境はそれについて何も実行できず、メモリを圧縮して空きブロックを結合することはできません。
ポインターがプログラマーに見えない概念であるプログラミング言語を使用している場合、Linuxでメモリの圧縮が可能です。もう1つの可能性は、戻り値がポインターではないメモリ割り当てAPIを使用することです。 Windowsでのハンドルベースのヒープ割り当て関数の例を参照してください(ポインターは、ハンドルがロックされている場合にのみ有効です)。
教授の演習では、フリーブロックウォーキングアルゴリズムを含むmmap
のパフォーマンスを効果的に測定しています。最初に3×mブロックを割り当て、次にそれらの半分を解放してから、m再びブロックします。これらのブロックをすべて解放すると、カーネルのアロケータに大量の空きブロックがダンプされます。これを追跡する必要があります(およびfree
呼び出しにかかる時間は、この時点では最適化が行われていないことを示しています)。個々のブロックの割り当て時間を追跡すると、最初の900kの割り当てに他のブロックよりもはるかに長い時間がかかることがわかります(3桁)私のシステムでは絶対値)、2番目ははるかに高速ですが、それでもずっと長くかかり(2桁)、3番目の割り当ては通常のパフォーマンスレベルに戻ります。だから何かが起こっていますが、返されたポインタは、それがメモリの圧縮ではなかったこと、少なくとも割り当てられたブロックの圧縮ではないことを示しています(前述のように、これは不可能です)—おそらく、カーネルが使用するデータ構造の処理時間に対応しますプロセスで使用可能なアドレス空間を追跡します(これを確認しており、後で更新します)。これらの長い割り当ては、測定している全体的な割り当てシーケンスを小さくする可能性があります。これは、900kの割り当てが800kの割り当てよりも全体的に長くかかる場合です。
オーバーコミットによって表示される動作が変わる理由は、演習が純粋にアドレススペースの操作から実際のメモリの割り当てに変わり、プレイグラウンドのサイズが小さくなるためです。オーバーコミットできる場合、カーネルはプロセスのアドレス空間によってのみ制限されるため、はるかに多くのブロックを割り当てて、アロケータにはるかに多くの圧力をかけることができます。オーバーコミットを無効にすると、カーネルは使用可能なメモリによって制限されます。これにより、m
に設定できる値が、アロケーターに十分な負荷がかかっていないため、割り当て時間が長くなります。