「メモリの断片化」という用語は、C++の動的メモリ割り当てのコンテキストで数回使用されていると聞きました。メモリの断片化に対処する方法についていくつか質問を見つけましたが、それ自体を扱う直接的な質問は見つかりません。そう:
また:
空きメモリの「大」(32バイト)の広がりがあると想像してください:
----------------------------------
| |
----------------------------------
次に、その一部を割り当てます(5つの割り当て):
----------------------------------
|aaaabbccccccddeeee |
----------------------------------
次に、最初の4つの割り当てを解放しますが、5番目の割り当ては解放しません。
----------------------------------
| eeee |
----------------------------------
次に、16バイトを割り当ててみます。ほぼ2倍の空きがありますが、できません。
仮想メモリを備えたシステムでは、断片化は想像よりも問題になりません。大きな割り当ては、物理アドレス空間ではなく、仮想アドレス空間でのみ連続する必要があるためです。したがって、私の例では、ページサイズが2バイトの仮想メモリがあれば、問題なく16バイトを割り当てることができます。物理メモリは次のようになります。
----------------------------------
|ffffffffffffffeeeeff |
----------------------------------
一方、仮想メモリ(はるかに大きい)は次のようになります。
------------------------------------------------------...
| eeeeffffffffffffffff
------------------------------------------------------...
メモリーの断片化の典型的な症状は、大きなブロックを割り当てようとすると、十分な空きメモリーがあるように見えてもできないことです。もう1つの考えられる結果は、プロセスがメモリをOSに戻すことができないことです(OSから割り当てたすべてのブロックでまだ使用されているオブジェクトがあるため、それらのブロックは現在ほとんど使用されていません)。
C++でメモリの断片化を防ぐための戦術は、サイズや予想される寿命に応じて異なる領域からオブジェクトを割り当てることにより機能します。したがって、大量のオブジェクトを作成し、後でそれらをすべて一緒に破棄する場合は、それらをメモリプールから割り当てます。それらの間に行う他の割り当てはプールからのものではないため、メモリ内でそれらの間に配置されないため、結果としてメモリは断片化されません。
一般的に、プログラムが長時間実行され、多くの割り当てと解放を行わない限り、あまり心配する必要はありません。短命と長命のオブジェクトが混在している場合は、最も危険にさらされますが、それでもmalloc
は最善を尽くして支援します。基本的に、プログラムに割り当てエラーが発生するか、予期せずにシステムのメモリが不足するまで無視します(テストでこれをキャッチしてください!)。
標準ライブラリは、メモリを割り当てる他のどのライブラリよりも悪くはありません。標準コンテナにはすべて、絶対に必要な場合に割り当て戦略を微調整するために使用できるAlloc
テンプレートパラメータがあります。
メモリの断片化とは
メモリの断片化とは、ほとんどのメモリが多数の非連続ブロックまたはチャンクに割り当てられている場合です。メモリ全体のかなりの割合が未割り当てですが、ほとんどの典型的なシナリオでは使用できません。これにより、メモリ不足例外、または割り当てエラーが発生します(つまり、mallocがnullを返します)。
これについて考える最も簡単な方法は、さまざまなサイズの写真を配置する必要がある大きな空の壁があると想像することです。それぞれの写真は特定のサイズを占めており、明らかにそれを小さなピースに分割してフィットさせることはできません。壁の空きスペース、写真のサイズが必要です。さもないと、設置できません。今、壁に写真をぶら下げ始めて、それらの配置方法に注意を払わないと、すぐに部分的に写真で覆われた壁になり、ほとんどの新しい写真が収まらない空のスポットがあっても使用可能なスポットよりも大きいためです。本当に小さな写真を掛けることはできますが、ほとんどの写真は収まりません。したがって、壁にすでにあるものを再配置(コンパクト)して、さらにスペースを空ける必要があります。
ここで、壁が(ヒープ)メモリであり、写真がオブジェクトであると想像してください。それがメモリの断片化です。
メモリの断片化がアプリケーションの問題であるかどうかをどのように確認できますか?どのようなプログラムが最も影響を受ける可能性がありますか?
メモリの断片化に対処している可能性のある兆候は、特に使用済みメモリの割合が高い場合に多くの割り当てエラーが発生する場合ですが、まだすべてのメモリを使い果たしていないため、技術的には十分なスペースが必要です割り当てようとしているオブジェクトに対して。
メモリが非常に断片化されている場合、メモリアロケータは新しいオブジェクトに適したスペースを見つけるためにより多くの作業を行う必要があるため、メモリの割り当てに時間がかかる可能性があります。順番に多くのメモリ割り当てがある場合(おそらくメモリフラグメンテーションで終わったので、おそらく)、割り当て時間は顕著な遅延を引き起こす可能性があります。
メモリの断片化に対処する良い一般的な方法は何ですか?
メモリの割り当てに適切なアルゴリズムを使用します。多数の小さなオブジェクトにメモリを割り当てる代わりに、それらの小さなオブジェクトの連続した配列にメモリを事前に割り当てます。メモリを割り当てるときに少し無駄になることもあるため、パフォーマンスが向上し、メモリの断片化に対処する手間を省くことができます。
メモリの断片化は、ディスクの断片化と同じ概念です。使用中の領域が十分に密集していないため、無駄になっている領域を指します。
簡単なおもちゃの例で、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バイト以下のものを割り当てない限り、3〜5バイトは未使用のままになります。
この例では、3バイトは無駄にはなりませんが、たとえば、メモリ内で10メガバイト離れた2バイトの割り当てが2つあり、サイズが10メガバイトのブロックを割り当てる必要がある、より病理学的なケースを考えてください。 + 1バイト。既に十分なスペースがあるのに1バイトしかかからないのに、OSに10メガバイト以上の仮想メモリを追加してもらう必要があります。
どうやって防ぐのですか?最悪のケースは、小さなオブジェクトを頻繁に作成および破棄するときに発生する傾向があります。これは、多くの小さなオブジェクトが多くの小さな穴で区切られた「スイスチーズ」効果を生み出し、それらの穴に大きなオブジェクトを割り当てることができないためです。これを行うことがわかっている場合、効果的な戦略は、メモリの大きなブロックを小さなオブジェクトのプールとして事前に割り当ててから、そのブロック内で小さなオブジェクトの作成を手動で管理することですデフォルトのアロケーターがそれを処理します。
一般的に、割り当てる割り当てが少ないほど、メモリが断片化される可能性は低くなります。ただし、STLはこれをかなり効果的に処理します。現在の割り当て全体を使用している文字列があり、それに1文字を追加する場合、単に現在の長さに1を加えたものに再割り当てするのではなく、doublesその長さ。これは、「頻繁に小さな割り当てのためのプール」戦略のバリエーションです。文字列は大きなメモリチャンクを取得しているため、小さな再割り当てを繰り返すことなく、サイズの小さな増加の繰り返しに効率的に対処できます。実際、すべてのSTLコンテナーはこのようなことを行うため、通常、STLコンテナーの自動再割り当てによって生じる断片化についてあまり心配する必要はありません。
もちろん、STLコンテナは相互にメモリをプールしないbetweenので、多数の小さなコンテナを作成する場合(少数のコンテナではなく頻繁にサイズ変更されます)、頻繁に作成される小さなオブジェクト(STLかどうか)の場合と同じように、断片化の防止に注意する必要があります。
- メモリの断片化とは何ですか?
メモリの断片化は、理論的には使用可能であっても、メモリが使用できなくなる問題です。フラグメンテーションには次の2種類があります:内部フラグメンテーションは割り当てられているが使用できないメモリです(たとえば、メモリが8バイトのチャンクで割り当てられているが、プログラムが4バイトしか必要としない場合に単一の同盟を繰り返します)。 外部フラグメンテーションは、空きメモリが十分にあるにもかかわらず大きな割り当て要求に対応できないように、空きメモリが多くの小さなチャンクに分割される問題です。
- メモリの断片化がアプリケーションの問題であるかどうかを確認するにはどうすればよいですか?どのようなプログラムが最も被害を受けやすいですか?
プログラムが実際のPaylodデータが必要とするよりもはるかに多くのシステムメモリを使用している場合(およびメモリリークを除外した場合)、メモリの断片化は問題です。
- メモリの断片化に対処する一般的な方法は何ですか?
適切なメモリアロケーターを使用します。 IIRC、「ベストフィット」戦略を使用するものは、少し遅くても、一般的に断片化の回避に非常に優れています。ただし、どの割り当て戦略でも、病理学的最悪のケースがあることが示されています。幸いなことに、ほとんどのアプリケーションの典型的な割り当てパターンは、実際にはアロケーターが比較的良性です。あなたが詳細に興味があるなら、そこにたくさんの論文があります:
更新:
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
に切り替えたときに状況を改善しました。メモリの断片化を特定できれば、それについてより正確に話すことができるかもしれません。
メモリの断片化に関する非常に詳細な回答は、ここにあります。
http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/
これは、softwareverify.comでメモリフラグメンテーションについて質問する人々に私が提供してきた11年間のメモリフラグメンテーションの答えの集大成です。
メモリの断片化は、さまざまなサイズの多くのオブジェクトを割り当ててdeallocateするときに発生する可能性が最も高くなります。メモリに次のレイアウトがあるとします。
obj1 (10kb) | obj2(20kb) | obj3(5kb) | unused space (100kb)
現在、obj2
がリリースされると、120kbの未使用メモリがありますが、メモリが断片化されているため、120kbの完全なブロックを割り当てることはできません。
この影響を回避する一般的な手法には、 リングバッファー および オブジェクトプール があります。 STLのコンテキストでは、 std::vector::reserve()
などのメソッドが役立ちます。
これは、ダミー用の非常に簡略化されたバージョンです。
オブジェクトがメモリ内に作成されると、メモリ内の使用済み部分の最後に追加されます。
メモリの使用済み部分の最後にないオブジェクトが削除された場合、つまりこのオブジェクトが他の2つのオブジェクトの間にあった場合、「穴」が作成されます。
これは、フラグメンテーションと呼ばれるものです。
メモリの断片化とは何ですか?
アプリが動的メモリを使用する場合、メモリのチャンクを割り当てて解放します。最初は、アプリのメモリスペース全体が空きメモリの連続した1ブロックです。ただし、異なるサイズのブロックを割り当てて解放すると、メモリはfragmentedを取得し始めます。つまり、大きな連続した空きブロックと多数の連続したブロックの代わりに割り当てられたブロックには、割り当てられたブロックと空きブロックが混在します。空きブロックのサイズは限られているため、再利用することは困難です。例えば。 1000バイトの空きメモリがある場合でも、100バイトブロックにメモリを割り当てることはできません。これは、すべての空きブロックの長さが最大で50バイトであるためです。
別の避けられないが、問題の少ない断片化の原因は、ほとんどのアーキテクチャで、メモリアドレスがalignedに2、4、8などのバイト境界(すなわち、アドレスは2、4、8などの倍数である必要があります。)これは、たとえば3つのchar
フィールドを含む構造体。各フィールドは4バイト境界に揃えられているため、構造体のサイズは3ではなく12になります。
メモリの断片化がアプリケーションの問題であるかどうかを確認するにはどうすればよいですか?どのようなプログラムが最も被害を受けやすいですか?
明らかな答えは、メモリ不足の例外が発生するということです。
どうやら、C++アプリでメモリの断片化を検出するための優れたポータブルな方法はありません。詳細については this answer をご覧ください。
メモリの断片化に対処する一般的な方法は何ですか?
ポインターで直接メモリアドレスを使用し、特定のメモリアドレスを参照するユーザーを制御できないため、C++では困難です。したがって、割り当てられたメモリブロックを再配置する(Javaガベージコレクタが行う方法)はオプションではありません。
カスタムアロケータは、大きなメモリチャンク内の小さなオブジェクトの割り当てを管理し、そのチャンク内の空きスロットを再利用することで役立ちます。
ヒープにアイテムを追加する場合、コンピュータはそのアイテムに合うようにスペースを検索する必要があります。そのため、メモリプールまたはプールされたアロケータを使用しない動的割り当てでは、処理が「遅く」なる可能性があります。重いSTLアプリケーションでは、マルチスレッドを実行している場合、 Hoard allocator または TBB Intel バージョンがあります。
現在、メモリが断片化されると、次の2つのことが起こります。
サイズの異なるメモリブロックが要求されるため、メモリの断片化が発生します。 100バイトのバッファーを考慮してください。 2つの文字、次に整数を要求します。ここで、2つの文字を解放し、新しい整数を要求しますが、その整数は2つの文字のスペースに収まりません。そのメモリは、再割り当てするのに十分な大きさの連続したブロックにないため、再利用できません。それに加えて、charsに対して多くのアロケーターオーバーヘッドを呼び出しました。
基本的に、メモリはほとんどのシステムで特定のサイズのブロックでのみ提供されます。これらのブロックを分割すると、ブロック全体が解放されるまで再結合できません。これにより、実際にはブロックのごく一部しか使用されていない場合に、ブロック全体が使用される可能性があります。
ヒープの断片化を減らす主な方法は、より大きく、頻度の低い割り当てを行うことです。極端な場合、少なくとも独自のコード内でオブジェクトを移動できるマネージヒープを使用できます。これは、メモリの観点からとにかく、問題を完全に排除します。明らかにオブジェクトの移動などにはコストがかかります。実際には、ヒープから非常に少量を頻繁に割り当てる場合にのみ、実際に問題が発生します。連続するコンテナ(ベクトル、文字列など)を使用し、可能な限りスタック上に割り当てる(常にパフォーマンスの良いアイデア)ことは、それを減らす最良の方法です。これにより、キャッシュの一貫性も向上し、アプリケーションの実行が高速になります。
覚えておくべきことは、32ビットx86デスクトップシステムでは、2GBのメモリ全体が4KBの「ページ」に分割されていることです(ページサイズはすべてのx86システムで同じであることを確認してください)。問題を起こすには、いくつかのomgwtfbbqフラグメンテーションを呼び出す必要があります。断片化は本当に過去の問題です。なぜなら、現代のヒープは大多数のアプリケーションにとって非常に大きく、マネージドヒープなど、それに耐えられるシステムが普及しているためです。