メモリの断片化とは何ですか?


203

「メモリの断片化」という用語は、C ++の動的メモリ割り当てのコンテキストで数回使用されたと聞いています。メモリの断片化を処理する方法についていくつかの質問を見つけましたが、それ自体を処理する直接的な質問は見つかりません。そう:

  • メモリの断片化とは何ですか?
  • アプリケーションのメモリの断片化が問題であるかどうかを確認するにはどうすればよいですか?どのようなプログラムが最も苦しみそうですか?
  • メモリの断片化に対処するための良い一般的な方法は何ですか?

また:

  • 動的割り当てを使用すると、メモリの断片化が増える可能性があると聞きました。これは本当ですか?C ++のコンテキストでは、すべての標準コンテナー(std :: string、std :: vectorなど)が動的メモリ割り当てを使用することを理解しています。これらがプログラム全体で使用される場合(特にstd :: string)、メモリの断片化が問題になる可能性が高くなりますか?
  • STLの多いアプリケーションでメモリの断片化をどのように処理できますか?

1
たくさんの素晴らしい答え、みんなに感謝!
AshleysBrain

4
そこ偉大な答えの多くはすでにあるが、ここではメモリの断片化が大きな問題だった実際のアプリケーション(Firefoxの)からいくつかの写真があります:blog.pavlov.net/2007/11/10/memory-fragmentation
マリウスGedminas

2
@MariusGedminasリンクが機能しなくなったため、リンクと一緒に簡単な概要を提供するか、リンクを含めて質問に回答することが重要です
katta

確かに、半年以上経過しています
rsethc

3
以下は、マリウスが投稿したリンクの更新された場所です:pavlovdotnet.wordpress.com/2007/11/10/memory-fragmentation
TheGameiswar

回答:


312

「大容量」(32バイト)の空きメモリがあるとします。

----------------------------------
|                                |
----------------------------------

次に、その一部を割り当てます(5つの割り当て)。

----------------------------------
|aaaabbccccccddeeee              |
----------------------------------

ここで、最初の4つの割り当てを解放しますが、5番目の割り当ては解放しません。

----------------------------------
|              eeee              |
----------------------------------

次に、16バイトを割り当ててみます。ああ、それはできません。でも、空き容量は2倍近くあります。

仮想メモリを備えたシステムでは、物理アドレス空間ではなく仮想アドレス空間で連続して大規模な割り当てを行うだけでよいので、断片化は想像以上に問題ではありません。したがって、私の例では、ページサイズが2バイトの仮想メモリがある場合、16バイトの割り当てを問題なく行うことができます。物理メモリは次のようになります。

----------------------------------
|ffffffffffffffeeeeff            |
----------------------------------

一方、仮想メモリ(はるかに大きい)は次のようになります。

------------------------------------------------------...
|              eeeeffffffffffffffff                   
------------------------------------------------------...

メモリの断片化の典型的な症状は、十分なメモリが解放されているように見えても、大きなブロックを割り当てようとしてもできないことです。別の考えられる結果は、プロセスがメモリをOSに解放できないことです(OSから割り当てたすべてのブロックでまだ使用中のオブジェクトがあるため、これらのブロックは現在ほとんど使用されていません)。

C ++でのメモリの断片化を防ぐための戦術は、サイズや期待される寿命に応じて、さまざまな領域からオブジェクトを割り当てることによって機能します。したがって、多くのオブジェクトを作成して後ですべて一緒に破棄する場合は、それらをメモリプールから割り当てます。それらの間で行う他の割り当てはプールからのものではないため、メモリ内のそれらの間には配置されないため、結果としてメモリは断片化されません。

プログラムが長時間実行されていて、多くの割り当てと解放を行わない限り、通常はそれほど心配する必要はありません。リスクが最も高いのは、存続期間の短いオブジェクトと存続期間の長いオブジェクトが混在している場合ですが、それでもmalloc役立つように最善を尽くします。基本的に、プログラムに割り当てエラーが発生するか、予期せずシステムのメモリ不足が発生するまで、これを無視します(テストでこれをキャッチしてください!)。

標準ライブラリは、メモリを割り当てる他の何よりも悪くはありません。標準コンテナにはすべてAlloc、絶対に必要な場合に割り当て戦略を微調整するために使用できるテンプレートパラメータがあります。


1
では、各文字は1バイトですか?これは、「大きな拡張」== 32バイトになります(私は推測しています-カウントされませんでした):)良い例ですが、最後の行の前に単位を記載すると役立ちます。:)
jalf

1
@jalf:はい。ユニットについてはまったく触れませんでしたが、最後に気づきました。あなたがコメントしている間、それに取り組んでいました。
スティーブジェソップ2010

「答え」を選択するのは非常に困難でした。ここにはすばらしい答えがたくさんあります。興味のある方は、それらすべてを読むことをお勧めします。それでも、あなたはここですべての重要なポイントをカバーしたと思います。
AshleysBrain

1
「標準ライブラリは、メモリを割り当てる他の何よりも悪いことではありません。」それが本当ならそれでいいのですが、文字列やベクトルなどの標準C ++テンプレートの実装は、サイズ変更時に非常に望ましくない動作をする可能性があります。たとえば、以前のバージョンのVisual Studioでは、std :: stringは基本的にrealloc 1.5 * current_size(最も近い8バイトに)によってサイズ変更されます。したがって、文字列に追加し続けると、特に組み込みシステムでは、ヒープを非常に簡単に消滅させることができます。最善の防御策は、隠れた再割り当てを回避するために、予想される使用量を確保することです。
ロッカ2018年

1
@ du369:仮想メモリは、物理メモリほどひどく断片化されていません。ffffffffffffffff仮想メモリ内の連続した割り当てですが、物理メモリ内にそのような連続した割り当ては存在できません。同じように断片化されているが、仮想空間の方がはるかに大きいことを確認したい場合は、代わりにそのように見てください。重要な実用的なポイントは、膨大な仮想アドレス空間を使用することで断片化を無視できることがよくあるため、16バイトの割り当てを可能にするときに役立ちます。
スティーブジェソップ

73

メモリの断片化とは何ですか?

メモリの断片化とは、ほとんどのメモリが連続していない多数のブロックまたはチャンクに割り当てられている場合です。メモリ全体のかなりの割合が未割り当てのままですが、ほとんどの一般的なシナリオでは使用できません。これにより、メモリ不足の例外または割り当てエラーが発生します(つまり、mallocはnullを返します)。

これについて考える最も簡単な方法は、さまざまなサイズの写真を置く必要がある大きな空の壁があると想像することです。各画像は特定のサイズを占め、明らかにそれを小さくするために分割することはできません。あなたは壁に空の場所、写真のサイズが必要です、そうでなければあなたはそれを置くことができません。さて、壁に写真をぶら下げ始めて、それらをどのように配置するかについて注意しなければ、すぐに壁が部分的に写真で覆われ、空のスポットがあってもほとんどの新しい写真は収まらないでしょう。利用可能なスポットよりも大きいためです。本当に小さな写真を掛けることはできますが、ほとんどの写真は収まりません。そのため、すでに壁にあるものを再配置(コンパクト)して、より多くのスペースを確保する必要があります。

ここで、壁があなたの(ヒープ)メモリであり、写真がオブジェクトであると想像してください。それがメモリの断片化です。

アプリケーションのメモリの断片化が問題であるかどうかを確認するにはどうすればよいですか?どのようなプログラムが最も苦しみそうですか?

メモリの断片化に対処している可能性のある兆候は、多くの割り当てエラーが発生した場合、特に使用済みメモリの割合が高い場合ですが、まだすべてのメモリを使い切っていないため、技術的には十分なスペースが必要です割り当てようとしているオブジェクトに対して。

メモリが大幅に断片化されている場合、メモリアロケータは新しいオブジェクトに適したスペースを見つけるためにより多くの作業を行う必要があるため、メモリの割り当てに時間がかかる可能性があります。次に、多くのメモリ割り当てがある場合(おそらくメモリの断片化が発生してからそれを行います)、割り当て時間は顕著な遅延を引き起こす可能性さえあります。

メモリの断片化に対処するための良い一般的な方法は何ですか?

メモリの割り当てには適切なアルゴリズムを使用してください。多くの小さなオブジェクトにメモリを割り当てる代わりに、それらの小さなオブジェクトの連続した配列にメモリを事前に割り当てます。時々、メモリを割り当てるときに少し無駄が多いと、パフォーマンスが向上し、メモリの断片化に対処する手間を省くことができます。


10
+1。「壁に描かれた写真」というメタファーは本当に、本当に、はっきりしたものなので、提案した回答を削除しました。
10

絵の大きさはいろいろあるはずだということを強調していただければと思います。それ以外の場合、断片化は発生しません。
ビョルンポレックス

1
興味深いことに、メインメモリデータベースは最近、いくらか実用的なものになりつつあります(実際には大量のメモリが利用可能です)。このコンテキストでは、HDDの場合と同様に、RAMから連続したラインを読み取る方が、データが断片化されている場合よりもはるかに高速であることは注目に値します。
ビョルンポレックス

1
壁の写真と見た目はよく似ていますが、メインメモリは2次元ではありません。それでも、いい答えです、ありがとう。
AshleysBrain

24

メモリの断片化は、ディスクの断片化と同じ概念です。これは、使用中の領域が密集していないために無駄になっている領域を指します。

単純なおもちゃの例として、10バイトのメモリがあるとします。

 |   |   |   |   |   |   |   |   |   |   |
   0   1   2   3   4   5   6   7   8   9

次に、A、B、Cという名前の3つの3バイトブロックを割り当てます。

 | A | A | A | B | B | B | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

次に、ブロックBの割り当てを解除します。

 | A | A | A |   |   |   | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

次に、4バイトのブロックDを割り当てようとするとどうなりますか?さて、4バイトの空きメモリがありますが、4バイトの連続した空きメモリがないため、Dを割り当てることができません。Dを格納できたはずですが、できませんでしたので、これはメモリの非効率的な使用です。また、プログラム内の一部の変数がCを指している可能性が高いため、Cを移動してスペースを確保することはできません。また、これらの値をすべて自動的に検索して変更することもできません。

それが問題であることをどのようにして知っていますか?最大の兆候は、プログラムの仮想メモリサイズが、実際に使用しているメモリ量よりもかなり大きいことです。実際の例では、10バイトを超えるメモリがあるため、Dはバイト9から割り当てられ、バイト3〜5は、後で3バイト以下のサイズを割り当てない限り、未使用のままになります。

この例では、3バイトは無駄に多くはありませんが、2バイトの2つの割り当てが、たとえばメモリ内で10メガバイト離れており、サイズが10メガバイトのブロックを割り当てる必要があるというより病理学的なケースを考えてください。 + 1バイト。あなたはすでに十分なスペースを持っているのが1バイト恥ずかしいのですが、それを行うには、10メガバイト以上の仮想メモリをOSに要求する必要があります。

どのようにそれを防ぐのですか?最悪のケースは、小さなオブジェクトを頻繁に作成および破棄するときに発生する傾向があります。これは、多くの小さなオブジェクトが多数の小さな穴で分離された「スイスチーズ」効果を生み出し、それらの穴に大きなオブジェクトを割り当てることができないためです。これを実行することがわかっている場合、効果的な戦略は、小さなオブジェクトのプールとしてメモリの大きなブロックを事前に割り当ててから、そのブロック内での小さなオブジェクトの作成を手動で管理することです。デフォルトのアロケータがそれを処理します。

一般に、割り当てが少ないほど、メモリが断片化される可能性は低くなります。ただし、STLはこれをかなり効果的に処理します。現在の割り当ての全体を使用している文字列があり、それに1文字を追加した場合、現在の長さに1を加えただけではなく、長さが2倍になります。これは、「頻繁な小さな割り当てのためのプール」戦略のバリエーションです。文字列は、メモリの大きなチャンクを取得しているため、繰り返される小さな再割り当てを行わずに、繰り返される小さなサイズの増加に効率的に対処できます。実際、すべてのSTLコンテナーがこの種のことを行うので、通常、自動的にSTLコンテナーを再割り当てすることによって引き起こされる断片化について、あまり心配する必要はありません。

もちろん、STLコンテナは相互にメモリプールしないため、(頻繁にサイズ変更される少数のコンテナではなく)多数の小さなコンテナを作成する場合は、同じ方法で断片化を防止する必要があるかもしれません。頻繁に作成される小さなオブジェクトの場合は、STLかどうかに関係なく。


14
  • メモリの断片化とは何ですか?

メモリの断片化は、理論的には利用可能であるにもかかわらず、メモリが使用できなくなるという問題です。2種類の断片化があります。内部断片化は、割り当てられているが使用できないメモリです(たとえば、メモリが8バイトのチャンクで割り当てられているが、プログラムが4バイトしか必要としない場合に、プログラムが繰り返し単一の競合を行う場合)。外部の断片化とは、空きメモリが多数の小さなチャンクに分割され、全体的な空きメモリが十分にあるにもかかわらず大きな割り当て要求を満たせなくなるという問題です。

  • アプリケーションのメモリの断片化が問題であるかどうかは、どうすればわかりますか?どんなプログラムが最も苦しみそうですか?

プログラムが実際のペイロードデータが必要とするよりもはるかに多くのシステムメモリを使用する場合(およびメモリリークを除外した場合)、メモリの断片化が問題になります。

  • メモリの断片化に対処するための良い一般的な方法は何ですか?

適切なメモリアロケータを使用してください。IIRC、「ベストフィット」戦略を使用するものは、少し遅い場合でも、通常は断片化を回避するのにはるかに優れています。ただし、どの割り当て戦略でも、病理学的に最悪のケースがあることも示されています。幸い、ほとんどのアプリケーションの典型的な割り当てパターンは、アロケーターが処理するのに実際には比較的無害です。詳細に興味があるなら、たくさんの論文があります:

  • ポール・R・ウィルソン、マーク・S・ジョンストーン、マイケル・ニーリー、デビッド・ボレス。動的ストレージ割り当て:調査と批評的レビュー。1995メモリ管理に関する国際ワークショップの議事録、Springer Verlag LNCS、1995
  • マーク・S・ジョンストン、ポール・R・ウィルソン。メモリの断片化の問題:解決しましたか?ACM SIG-PLAN通知、ボリューム34 No. 3、ページ26-36、1999
  • MR Garey、RL Graham、JD Ullman。メモリ割り当てアルゴリズムの最悪のケースの分析。コンピューティング理論に関する第4回ACMシンポジウム、1972年

9

更新:
Google TCMalloc:スレッドキャッシングMalloc 長期実行プロセスでの断片化の処理には非常に優れ
ていることがわかっています。


HP-UX 11.23 / 11.31 ia64でメモリの断片化に問題があるサーバーアプリケーションを開発しています。

こんな感じでした。メモリの割り当てと割り当て解除を行い、数日間実行するプロセスがありました。また、メモリリークはありませんでしたが、プロセスのメモリ消費量は増加し続けました。

私の経験について。HP-UXでは、HP-UX gdbを使用してメモリの断片化を見つけるのは非常に簡単です。ブレークポイントを設定し、ヒットしたときに次のコマンドを実行しinfo heapます。プロセスのすべてのメモリ割り当てとヒープの合計サイズを確認します。次に、プログラムを続行し、しばらくしてから再びブレークポイントに到達します。もう一度やりますinfo heap。ヒープの合計サイズは大きくても、個別の割り当ての数とサイズが同じである場合は、メモリ割り当てに問題がある可能性があります。必要に応じて、これを数回前に確認してください。

私の状況を改善する方法はこれでした。HP-UX gdbで分析を行った後、メモリの問題がstd::vector、データベースからのある種の情報の格納に使用したことが原因であることがわかりました。std::vectorそのデータを1つのブロックに保持する必要があります。に基づくコンテナがいくつかありましたstd::vector。これらのコンテナは定期的に再作成されました。新しいレコードがデータベースに追加され、その後コンテナが再作成される状況がしばしばありました。また、再作成されたコンテナは大きかったため、空きメモリの使用可能なブロックに収まらず、ランタイムはOSから新しい大きなブロックを要求しました。その結果、メモリリークはありませんでしたが、プロセスのメモリ消費量が増加しました。容器を交換したところ状況が良くなりました。代わりにstd::vector私は使い始めましたstd::deque データにメモリを割り当てる別の方法があります。

HP-UXでのメモリの断片化を回避する方法の1つは、Small Block Allocatorを使用するか、MallocNextGenを使用することです。RedHat Linuxでは、デフォルトのアロケーターは多くの小さなブロックの割り当てをかなりうまく処理するようです。WindowsではLow-fragmentation Heap、多数の小さな割り当ての問題に対処します。

私の理解では、STLの多いアプリケーションでは、まず問題を特定する必要があります。(libcのような)メモリアロケーターは実際には多くの小さな割り当ての問題を処理しますが、これは一般的ですstd::string(たとえば、私のサーバーアプリケーションには多くのSTL文字列がありますが、実行からわかるように、info heapそれらは問題を引き起こしていません)。私の印象では、頻繁に大規模な割り当てを行う必要はありません。残念ながら、それらを回避できず、コードを変更する必要がある状況があります。私が言ったように、私はに切り替えたときに状況を改善しましたstd::deque。あなたがあなたの記憶の断片化を識別した場合、それをより正確に話すことが可能かもしれません。


6

さまざまなサイズの多くのオブジェクトを割り当てたり割り当て解除したりすると、メモリの断片化が発生する可能性が最も高くなります。メモリに次のレイアウトがあるとします。

obj1 (10kb) | obj2(20kb) | obj3(5kb) | unused space (100kb)

現在、obj2がリリースされると、120kbの未使用メモリがありますが、メモリが断片化されているため、120kbの完全なブロックを割り当てることはできません。

この影響を回避するための一般的な手法には、リングバッファーオブジェクトプールがあります。STLのコンテキストでは、などのメソッドstd::vector::reserve()が役立ちます。



3

メモリの断片化とは何ですか?

アプリが動的メモリを使用する場合、メモリのチャンクを割り当てて解放します。最初は、アプリのメモリ空間全体が、空きメモリの1つの連続したブロックです。ただし、異なるサイズのブロックを割り当てたり解放したりすると、メモリが断片化し始めます。つまり、大きな連続した空きブロックと多数の連続した割り当て済みブロックの代わりに、割り当てられたブロックと空きブロックが混ざってしまいます。空きブロックにはサイズの制限があるため、再利用することは困難です。たとえば、1000バイトの空きメモリがある場合でも、100バイトのブロックにメモリを割り当てることはできません。すべての空きブロックの長さが最大で50バイトであるためです。

別の避けられないが問題の少ない断片化の原因は、ほとんどのアーキテクチャで、メモリアドレスを2、4、8などのバイト境界に揃える必要があることです(つまり、アドレスは2、4、8などの倍数でなければなりません)。つまり、たとえば、3つのcharフィールドを含む構造体がある場合でも、各フィールドが4バイト境界に揃えられているため、構造体のサイズが3ではなく12になる場合があります。

アプリケーションのメモリの断片化が問題であるかどうかは、どうすればわかりますか?どんなプログラムが最も苦しみそうですか?

明白な答えは、メモリ不足の例外が発生することです。

どうやら、C ++アプリでメモリの断片化を検出するための優れたポータブルな方法はありません。詳細については、この回答を参照してください。

メモリの断片化に対処するための良い一般的な方法は何ですか?

C ++では、ポインターで直接メモリアドレスを使用し、特定のメモリアドレスを参照するユーザーを制御できないため、これは困難です。したがって、割り当てられたメモリブロックを再配置する(Javaガベージコレクタが行う方法)ことはできません。

カスタムアロケーターは、より大きなメモリチャンク内の小さなオブジェクトの割り当てを管理し、そのチャンク内の空きスロットを再利用することで役立つ場合があります。


3

これは、ダミー用の非常に簡略化されたバージョンです。

オブジェクトがメモリ内に作成されると、メモリ内の使用済み部分の最後に追加されます。

メモリの使用済み部分の最後にないオブジェクトが削除された場合、つまりこのオブジェクトが他の2つのオブジェクトの間にあった場合、「穴」が作成されます。

これは断片化と呼ばれるものです。


2

ヒープにアイテムを追加する場合、コンピュータがそのアイテムに合うようにスペースを検索する必要があります。これが、メモリプールで行われていない場合、またはプールされたアロケータを使用して行われていない場合、動的割り当てが「速度を低下させる」可能性がある理由です。重いSTLアプリケーションの場合、マルチスレッドを実行している場合、HoardアロケータまたはTBB Intelバージョンがあります。

ここで、メモリが断片化すると、2つのことが起こります。

  1. 「大きな」オブジェクトを貼り付けるための適切なスペースを見つけるには、さらに検索を行う必要があります。つまり、多くの小さなオブジェクトが散らばって、連続したメモリの適切なチャンクを見つけることは、特定の条件下では難しい場合があります(これらは極端です)。
  2. メモリは、簡単に読み取れるエンティティではありません。プロセッサーは、どれだけの容量と場所に制限されます。彼らは、必要なアイテムが1つの場所で現在のアドレスが別の場所である場合にページを交換することによってこれを行います。常にページをスワップする必要がある場合、処理が遅くなる可能性があります(これも、パフォーマンスに影響を与える極端なシナリオです)。仮想メモリに関するこの投稿を参照してください。

1

異なるサイズのメモリブロックが要求されるため、メモリの断片化が発生します。100バイトのバッファを考えます。2つの文字を要求し、次に整数を要求します。次に、2つの文字を解放し、新しい整数を要求します。ただし、その整数は2つの文字のスペースに収まりません。そのメモリは、再割り当てするのに十分な大きさの連続したブロックにないため、再利用できません。その上、charsに対して多くのアロケーターオーバーヘッドを呼び出しました。

基本的に、メモリはほとんどのシステムで特定のサイズのブロックでのみ提供されます。これらのブロックを分割すると、ブロック全体が解放されるまで再結合できません。これにより、実際にはブロックのごく一部のみが使用されているときに、ブロック全体が使用される可能性があります。

ヒープの断片化を減らす主な方法は、より大きく、頻度の少ない割り当てを行うことです。極端な場合、少なくとも自分のコード内でオブジェクトを移動できるマネージヒープを使用できます。これは問題を完全に排除します-とにかく、メモリの観点から。明らかにオブジェクトなどを移動するにはコストがかかります。実際には、ヒープから非常に少量を頻繁に割り当てる場合にのみ問題が発生します。連続するコンテナー(ベクター、文字列など)を使用し、人間が可能な限りスタックに割り当てる(常にパフォーマンスを向上させるための推奨)ことは、それを減らすための最良の方法です。これにより、キャッシュの一貫性も向上し、アプリケーションの実行が速くなります。

覚えておくべきことは、32ビットx86デスクトップシステムでは、2GBのメモリ全体が4KBの「ページ」に分割されるということです(ページサイズはすべてのx86システムで同じであることを確認してください)。問題が発生するには、omgwtfbbqフラグメンテーションを呼び出す必要があります。現代のヒープは大多数のアプリケーションに対して非常に大きいため、断片化は本当に過去の問題です。マネージヒープなど、それに耐えることができるシステムが普及しています。


0

どんなプログラムが最も苦しみそうですか?

メモリの断片化に関連する問題の良い(恐ろしい)例は、Stardockによるコンピュータゲーム「Elemental:War of Magic」の開発とリリースでした。

ゲームは32ビット/ 2 GBのメモリ用に構築されており、ゲームをこれらの2 GBのメモリ内で機能させるには、メモリ管理を大幅に最適化する必要がありました。「最適化」により一定の割り当てと割り当て解除が行われるため、時間の経過とともにヒープメモリの断片化が発生し、毎回 ゲームがクラッシュしまし

あり、「戦争の物語」インタビュー YouTubeには。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.