カーネルに断片化されたメモリを圧縮させる方法


8

私は走っていFedora 26ます。

これは、アルゴリズムの教授から与えられた非常に奇妙な割り当てです。割り当ては言う:

Cでのメモリの断片化:
以下を実行するCプログラムを設計、実装、および実行します。これは、3mそれぞれ800,000要素のサイズの配列のシーケンスにメモリを割り当てます。次に、すべての偶数番号の配列を明示的に割り当て解除し、mそれぞれサイズが900,000要素の配列のシーケンスを割り当てます。プログラムが最初のシーケンスの割り当てと2番目のシーケンスの割り当てに必要な時間を測定します。mプログラムで使用できるメインメモリのほぼすべてを使い果たすことを選択してください。」

これの全体的な目標は、メモリを断片化してから、連続したチャンクとして利用できる量よりもわずかに多くを要求し、オペレーティングシステムにメモリの最適化または最適化を強制することです。

クラスでは、メモリが視覚化されており、実際には隣接していないため、これをどのように行うべきかを尋ねました。彼は「[仮想メモリ]をオフにする必要があります」と答えました。他の何人かの学生は、クラスでこの「ガベージコレクション」に到達したときにどのように知るべきかを尋ね、彼は次のように述べました。

少し調べたところ、仮想メモリを無効にするのに最も近いのは、でスワップメモリ​​を無効にすることでしたswapoff -a。デスクトップ環境を無効にして、ネイティブターミナルからプログラムをコンパイルして実行しました(他のプロセス、特にデスクトップ環境のような重いプロセスからの干渉を避けるため)。私はこれをm行い、2番目の割り当てのタイミングが最初の割り当てよりも大きくなるポイントに到達するまで、プログラムを増やしながら実行しました。

私は増加してプログラムを実行し、m最終的に2番目の割り当ての時間が最初の割り当ての時間よりも多くなるポイントを見つけました。しかし途中で、2番目の割り当ての前にプロセスが強制終了されるポイントに到達しました。私dmesgはそれをチェックして、それがoom-killer によって殺されたことを確認しました。私はoom-killer に関するいくつかの記事を見つけて読み、カーネルによるメモリの過剰割り当てを無効にできることを発見しました。

私はこれを実行してプログラムを再度実行しましたが、今回のみm、2番目のタイミングが最初のタイミングよりも高いようなものを見つけることができませんでした。結局、mを大きくすると(割り当て超過が有効になっている場合よりもはるかに小さくなりますが)、mallocが失敗し、プログラムが終了します。

3つの質問があります。最初の質問はそれほど重要ではありません。

  1. ガベージコレクションはこれの正しい用語ですか?私の教授はこれはガベージコレクションであると言っていますが、ガベージコレクションはプログラミング言語によって行われるものであり、これはより最適化されていると考えられると思い込んでいました。

  2. Linuxシステムで彼が望んでいるような圧縮は可能ですか?

  3. スワップを無効にしても、メモリの割り当て超過が有効になっているときに、2番目の割り当ての時間が最初の割り当てよりも長くなる点に到達できたのはなぜですか?圧縮は実際に行われましたか?もしそうなら、メモリの割り当て超過を無効にした後、圧縮が発生するポイントに到達できなかったのはなぜですか?


仮想メモリを「オフ」にすることはできません。また、Linuxでは論理的に実際よりも多くのメモリを割り当てることができることに注意してください。カーネルは、ページに書き込むまで実際にはページを割り当てません。
アンディダルトン

3
私はさらに別の宿題を期待していた質問。しかし、これは私の宿題ですが、明確に指定されておらず、思慮深く、不可能です。それは...ですか?質問。これの一部はスタックオーバーフローの領域であり、(多くの場合、ランダムに1つの例を選択するために)stackoverflow.com/questions/4039274の行に沿って多くのQ&Aがあります。
JdeBP 2018

回答:


5

これまでの研究に対する称賛です。これは確かに興味深い一連の質問です。

ここで一般的に考慮すべき重要な側面があります。メモリ割り当ては、一部はオペレーティングシステムの責任であり、一部は実行中の各プロセスの責任です(メモリ保護と仮想アドレス空間のない古いシステムは無視します)。オペレーティングシステムは、各プロセスに独自のアドレススペースを提供し、必要に応じてプロセスに物理メモリを割り当てます。各プロセスは、アドレス空間を(ある程度)分割し、適切に使用されるようにします。ランタイム環境がほとんどのことを処理するので、プロセスの責任はプログラマにはほとんど見えないことに注意してください。

今、あなたの質問に答えるために...

  1. 私の考えでは、ガベージコレクションは、ここで行っていることから1ステップ削除されています。あなたがCで書いていて、とを使用しているmalloc()と思いますfree()ガベージコレクションは、プログラミング言語ランタイム環境でサポートされている場合、後の部分を処理します。これは、以前に割り当てられていたが使用されなくなったメモリブロックを識別し、重要なことに、再び使用することはできません)アロケータに。JdeBPコメントにリンクされている質問はいくつかの背景を提供しますが、ガベージコレクションについて、またガベージコレクションを構成するものについてさえ、人によって意見が大きく異なることを示しているため、興味深い点です。

    私たちが興味を持っているコンテキストでは、「メモリ圧縮」を使用して、議論中のプロセスについて話します。

  2. ユーザー空間のプログラミングの観点から見ると、Linuxでは、Cで教授が求めていることは不可能です。ここで重要なのは、物理メモリの断片化ではなく、アドレス空間の断片化です。800,000バイトのブロックを多数割り当てると、各ブロックへのポインタと同じ数のポインタが作成されます。Linuxでは、現時点ではオペレーティングシステム自体はあまり機能しておらず、各割り当てをサポートする物理メモリは必ずしも必要ではありません(余談ですが、割り当てが小さい場合、オペレーティングシステムはまったく関与しません。 Cライブラリのアロケータ;ただし、ここでの割り当ては、Cライブラリが使用するのに十分な大きさです。mmap、これはカーネルによって処理されます)。奇数番号のブロックを解放すると、アドレス空間のそれらのブロックが返されますが、他のブロックへのポインターを変更することはできません。ポインターを印刷していくと、それらの違いが割り当て要求(私のシステムでは802,816バイト)を超えていないことがわかります。900,000バイトブロックの2つのポインターの間にスペースはありません。プログラムには、より抽象的な値(他のコンテキストではハンドル)ではなく、各ブロックへの実際のポインターがあるため、ランタイム環境はそれについて何も実行できず、そのため、メモリを圧縮して空きブロックを結合することはできません。

    ポインターがプログラマーに見えない概念であるプログラミング言語を使用している場合、Linuxでメモリの圧縮が可能です。もう1つの可能性は、戻り値がポインターではないメモリ割り当てAPIを使用することです。Windowsでのハンドルベースのヒープ割り当て関数の例を参照してください(ポインターは、ハンドルがロックされている場合にのみ有効です)。

  3. 教授の演習mmapでは、フリーブロックウォーキングアルゴリズムを含むのパフォーマンスを効果的に測定しています。最初に3× mブロックを割り当て、次にそれらの半分を解放してから、mブロックの割り当てを再開します。それらすべてのブロックを解放すると、カーネルのアロケーターに膨大な量の空きブロックがダンプされます。これは追跡する必要があります(free呼び出しにかかる時間は、この時点では最適化が行われていないことを示しています)。あなたは、個々のブロックの割り当て時間を追跡する場合は、その後、最初の900Kの割り当てが多くを取ることがわかります多くを他のものよりも長い(私のシステムでは3桁)、2番目ははるかに高速ですが、それでもずっと長くかかり(2桁)、3番目の割り当ては通常のパフォーマンスレベルに戻ります。したがって、何かが起こっていますが、返されたポインタは、それがメモリ圧縮ではなかったこと、少なくとも割り当てられたブロック圧縮ではないことを示しています(これは、上で説明したように不可能です)—おそらく、時間はカーネルが使用するデータ構造の処理時間に対応しますプロセスで使用可能なアドレス空間を追跡します(これを確認しており、後で更新します)。これらの長い割り当ては、測定している割り当てシーケンス全体を小さくする可能性があります。これは、900kの割り当てが800kの割り当てよりも全体的に長くかかる場合です。

    オーバーコミットによって表示される動作が変わる理由は、演習が純粋にアドレススペースの操作から実際のメモリ割り当てに変わり、プレイグラウンドのサイズが小さくなるためです。オーバーコミットできる場合、カーネルはプロセスのアドレス空間によってのみ制限されるため、はるかに多くのブロックを割り当て、アロケータにはるかに多くの圧力をかけることができます。オーバーコミットを無効にすると、カーネルは使用可能なメモリによって制限されます。これによりm、アロケータに十分な負荷がかかっていないため、割り当て時間が長くなる可能性のあるレベルまで値を下げることができます。


calloc()を使用するか、割り当てられた配列に実際に書き込むと、ここで何か違いがありますか?
クサラナンダ

割り当てられたメモリに書き込むと、オーバーコミット機能がキャンセルされますが、障害が発生するとOOMキラーがステップインします(必ずしもオーバー割り当てプロセスを強制終了するわけではないため)。calloc()割り当てが大きい場合は、malloc()Linux と同じように動作し、mmap()匿名マッピングの割り当てに使用されます。匿名マッピングは、最初に使用されたときにゼロで埋められます(そのため、オーバーコミットは引き続き機能します)。
スティーブンキット2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.