16

Linux glibc アロケータの動作がおかしいようです。うまくいけば、誰かがこれに光を当てることができます。私が持っているソースファイルは次のとおりです。

最初の.cpp:

#include <unistd.h>
#include <stdlib.h>
#include <list>
#include <vector>

int main() {

  std::list<char*> ptrs;
  for(size_t i = 0; i < 50000; ++i) {
    ptrs.push_back( new char[1024] );
  }
  for(size_t i = 0; i < 50000; ++i) {
    delete[] ptrs.back();
    ptrs.pop_back();
  }

  ptrs.clear();

  sleep(100);

  return 0;
}

秒.cpp:

#include <unistd.h>
#include <stdlib.h>
#include <list>

int main() {

  char** ptrs = new char*[50000];
  for(size_t i = 0; i < 50000; ++i) {
    ptrs[i] = new char[1024];
  }
  for(size_t i = 0; i < 50000; ++i) {
    delete[] ptrs[i];
  }
  delete[] ptrs;

  sleep(100);

  return 0;
}

私は両方をコンパイルします:

$ g++ -o 最初の first.cpp
$ g++ -o 秒 second.cpp

最初に実行し、スリープ状態になった後、常駐メモリ サイズを確認します。

first.cpp をコンパイルして実行すると、ps でメモリが表示されます。

$ ./first&
$ ps aux | grep first
davidw    9393  1.3  0.3  64344 53016 pts/4    S    23:37   0:00 ./first


$ ./second&
$ ps aux | grep second
davidw    9404  1.0  0.0  12068  1024 pts/4    S    23:38   0:00 ./second

常駐メモリのサイズに注意してください。まず、常駐メモリのサイズは 53016k です。次に、1024k です。まず、なんらかの理由で割り当てをカーネルに解放しませんでした。

最初のプログラムはメモリをカーネルに解放しないのに、2 番目のプログラムは解放するのはなぜですか? 最初のプログラムはリンク リストを使用しており、リンク リストはおそらく、解放するデータと同じページにいくつかのノードを割り当てることを理解しています。ただし、これらのノードは解放する必要があります。これは、これらのノードをポップオフしてから、リンクされたリストをクリアするためです。これらのプログラムのいずれかを valgrind を介して実行すると、メモリ リークは発生しません。おそらく何が起こっているかというと、first.cpp ではメモリが断片化され、second.cpp では断片化されないということです。しかし、ページ上のすべてのメモリーが解放された場合、そのページが放棄されてカーネルに返されないのはなぜでしょうか? メモリが解放されてカーネルに戻るには何が必要ですか? メモリがカーネルに放棄されるように、first.cpp (char* をリストに追加し続ける) を変更するにはどうすればよいですか。

4

4 に答える 4

17

この動作は意図的なものであり、glibc がメモリを実際にシステムに返すかどうか、または後で再利用するためにキャッシュするかどうかを決定するために使用する調整可能なしきい値があります。最初のプログラムでは、それぞれに多くの小さな割り当てを行いますがpush_back、それらの小さな割り当ては連続したブロックではなく、おそらくしきい値を下回っているため、OS に返されません。

リストをクリアした後に呼び出すmalloc_trim(0)と、glibc はすぐに空きメモリの一番上の領域をシステムに返す必要があります (sbrk次にメモリが必要になったときにシステム コールが必要になります)。

デフォルトの動作を本当にオーバーライドする必要がある場合 (プロファイリングで実際に役立つことが明らかにならない限り、これはお勧めしません)、おそらく strace を使用 mallinfoしたり、プログラムで実際に何が起こっているかを実験したりmallopt、しきい値を調整するためにを使用したりする必要があります。システムにメモリを返すため。

于 2012-06-08T08:42:58.607 に答える
5

再度リクエストした場合に備えて、小さいチャンクを利用できるようにします。これは単純なキャッシュの最適化であり、心配する必要のある動作ではありません。

于 2012-06-08T07:53:54.250 に答える
3

通常、によって割り当てられたメモリはnew、プロセスが終了したときにのみシステムに返されます。2番目のケースでlibcは、非常に大きな連続ブロックに特別なアロケーターを使用していると思われますが、それはそれを返しますが、あなたのいずれかが返されたら非常に驚くでしょう.new char[1024]多くのUnixでは、大きなブロックでさえ返されます。

于 2012-06-08T07:52:51.193 に答える
2

(ここには本当に問題がないので、私の答えを編集します。)

指摘されているように、ここでは実際には問題はありません。ジョナソン・ウェイクリーが頭に釘を打ちます。

メモリ使用率が Linux で期待したものと異なる場合、通常はmtraceツールを使用して分析を開始し、/proc/self/mapsファイルを分析します。

mtraceは、トレースを開始する呼び出しとトレースを終了する呼び出しの 2 つの呼び出しをコードで囲むことによって使用されます。

  mtrace();
  {
      // do stuff
  }
  muntrace();

呼び出しは、環境変数が設定されmtraceている場合にのみアクティブになります。MALLOC_TRACEmtrace ログ出力のファイル名を指定します。このログ出力は、メモリ リークについて分析できます。と呼ばれるコマンド ライン プログラムmtraceを使用して、出力を分析できます。

$ MALLOC_TRACE=mtrace.log ./a.out
$ mtrace ./a.out mtrace.log

この/proc/self/mapsファイルは、匿名領域を含む、現在のプログラムで使用されているメモリ マップ領域のリストを提供します。特に大きな領域を特定するのに役立ちますが、その領域が何に関連付けられているかを判断するには、追加の調査が必要です。/proc/self/maps以下は、ファイルを別のファイルにダンプする簡単なプログラムです。

void dump_maps (const char *outfilename) {
  std::ifstream inmaps("/proc/self/maps");
  std::ofstream outf(outfilename, std::ios::out|std::ios::trunc);
  outf << inmaps.rdbuf();
}
于 2012-06-08T08:50:34.313 に答える