243

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

  • メモリの断片化とは何ですか?
  • メモリの断片化がアプリケーションの問題かどうかを判断するにはどうすればよいですか? どのようなプログラムが被害を受ける可能性が最も高いですか?
  • メモリの断片化に対処するための良い一般的な方法は何ですか?

また:

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

12 に答える 12

350

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

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

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

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

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

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

ここで、16 バイトを割り当ててみます。おっと、できません。空き容量が 2 倍近くあるのに。

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

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

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

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

メモリの断片化の典型的な症状は、十分な空きメモリがあるように見えても、大きなブロックを割り当てようとして割り当てられないことです。もう 1 つの考えられる結果は、プロセスがメモリを OS に戻すことができないことです (OS から割り当てられた大きなブロックのそれぞれに、malloc細分化などのために、各ブロックのほとんどが残っているにもかかわらず、何かが残っているため)。現在は未使用)。

C++ でメモリの断片化を防ぐための戦術は、オブジェクトのサイズや予想される有効期間に応じて、さまざまな領域からオブジェクトを割り当てることによって機能します。したがって、大量のオブジェクトを作成し、後でまとめて破棄する場合は、それらをメモリ プールから割り当てます。それらの間で行う他の割り当てはプールからのものではないため、メモリ内のそれらの間に配置されないため、結果としてメモリが断片化されることはありません。または、同じサイズのオブジェクトを多数割り当てる場合は、それらを同じプールから割り当てます。次に、プール内の空き領域のストレッチは、そのプールから割り当てようとしているサイズよりも小さくなることはありません。

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

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

于 2010-09-22T15:02:52.050 に答える
93

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

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

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

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

メモリの断片化がアプリケーションの問題かどうかを判断するにはどうすればよいですか? どのようなプログラムが被害を受ける可能性が最も高いですか?

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

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

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

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

于 2010-09-22T15:00:27.807 に答える
26

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

簡単なおもちゃの例で、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バイト恥ずかしがり屋ですが、それを行うには、OSに10メガバイトを超える仮想メモリを要求する必要があります。

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

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

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

于 2010-09-22T15:10:20.343 に答える
14
  • メモリの断片化とは何ですか?

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

  • メモリの断片化がアプリケーションの問題かどうかを判断するにはどうすればよいですか? どのようなプログラムが被害を受ける可能性が最も高いですか?

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

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

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

  • ポール・R・ウィルソン、マーク・S・ジョンストーン、マイケル・ニーリー、デビッド・ボールズ。動的ストレージ割り当て: 調査と重要なレビュー。メモリ管理に関する 1995 年国際ワークショップの議事録、Springer Verlag LNCS、1995 年
  • マーク・S・ジョンストーン、ポール・R・ウィルソン。メモリの断片化の問題: 解決済み? ACM SIG-PLAN Notices、第 34 巻第 3 号、26 ~ 36 ページ、1999 年
  • MR ゲイリー、RL グラハム、JD ウルマン。メモリ割り当てアルゴリズムの最悪の場合の分析。コンピューティング理論に関する第 4 回年次 ACM シンポジウム、1972 年
于 2010-09-22T15:04:25.823 に答える
10

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


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

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

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

状況を改善する私の方法はこれでした。std::vectorHP-UX gdb を使用して分析を行ったところ、データベースからのある種の情報を格納するために HP-UX gdb を使用したことが原因でメモリの問題が発生していることがわかりました。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。記憶の断片化を特定できれば、それについてより正確に話すことができるかもしれません。

于 2010-09-22T15:46:53.750 に答える
6

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

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

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

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

于 2010-09-22T15:01:56.107 に答える
3

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

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

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

メモリの断片化がアプリケーションの問題かどうかを判断するにはどうすればよいですか? どのようなプログラムが被害を受ける可能性が最も高いですか?

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

どうやら、C++ アプリでメモリの断片化を検出するための適切な移植可能な方法はありません。詳細については、この回答を参照してください。

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

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

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

于 2010-09-22T15:02:11.777 に答える
3

これはダミー用の超簡略版です。

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

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

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

于 2010-09-22T16:27:57.217 に答える
2

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

ここで、メモリが断片化されると、次の 2 つのことが発生する可能性があります。

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

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

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

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

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

于 2010-09-22T15:09:28.750 に答える