私は、頻繁にメモリの割り当てと割り当て解除を行う、長持ちするアプリケーションを使用しています。 mallocの実装は、解放されたメモリをシステムに戻しますか?
この点で、次の動作は何ですか?
メモリ使用量が昼間と夜間で大きく異なる可能性があるアプリケーションがある場合(たとえば)、解放されたメモリをシステムに戻すようにmallocのいずれかを強制できますか?
そのような戻りがないと、解放されたメモリはスワップアウトされ、何度も交換されますが、そのようなメモリにはゴミしか含まれません。
次の分析は、glibc(ptmalloc2アルゴリズムに基づく)にのみ適用されます。解放されたメモリをシステムに戻すのに役立つと思われる特定のオプションがあります。
mallopt() (_malloc.h
_で定義)は、パラメータオプション_M_TRIM_THRESHOLD
_の1つを使用してトリムしきい値を設定するオプションを提供します。これは、空きメモリの最小量を示します(バイト単位)データセグメントの先頭で使用できます。量がこのしきい値を下回ると、glibcはbrk()
を呼び出して、カーネルにメモリを戻します。
Linuxの_M_TRIM_THRESHOLD
_のデフォルト値は128Kに設定されています。値を小さくすると、スペースを節約できます。
同じ動作は、環境変数_MALLOC_TRIM_THRESHOLD_
_にトリムしきい値を設定することで達成でき、ソースはまったく変更されません。
ただし、_M_TRIM_THRESHOLD
_を使用して実行された予備のテストプログラムは、mallocによって割り当てられたメモリがシステムに返されても、brk()
を介して最初に要求された実際のメモリチャンク(アリーナ)の残りの部分を示しています。保持される傾向があります。
malloc_trim(pad)
(_malloc.h
_で定義)を呼び出すことにより、メモリアリーナをトリムし、未使用のメモリをシステムに戻すことができます。この関数は、データセグメントのサイズを変更し、少なくともpad
バイトを最後に残して、解放できるページが1ページ未満の場合に失敗します。セグメントサイズは常に1ページの倍数であり、i386では4,096バイトです。
_malloc_trim
_を使用したfree()
のこの変更された動作の実装は、mallocフック機能を使用して実行できます。これは、コアglibcライブラリへのソースコードの変更を必要としません。
glibcの無料実装内でmadvise()
システムコールを使用します。
ほとんどの実装では、「OS」に適したサイズの「ブロック」全体が解放されて返される可能性がある(比較的まれな)ケースを特定する必要はありませんが、もちろん例外もあります。たとえば、OpenBSDの wikipedia page から引用します。
free
を呼び出すと、munmapを使用してメモリが解放され、プロセスアドレス空間からマップ解除されます。このシステムは、OpenBSDのmmap
システムコールの一部として実装されたアドレス空間レイアウトのランダム化とギャップページ機能を利用してセキュリティを向上させ、解放後のバグを検出するように設計されています。解放後、完全にマップ解除され、それ以上使用すると、セグメンテーション違反が発生し、プログラムが終了します。
ただし、ほとんどのシステムはOpenBSDほどセキュリティを重視していません。
これを知って、大量のメモリに対する一時的であることがわかっている要件がある長期実行システムをコーディングしているときは、常にプロセスをfork
しようとします。親は次に待機します子からの結果[[通常はパイプ上]]、子が計算(メモリ割り当てを含む)を実行し、結果を[[パイプ上]]で返し、終了します。このようにして、私の長期実行プロセスは、メモリの需要が時折「急増」するまでの長い間、無駄にメモリを消費することはありません。他の代替戦略には、そのような特別な要件のためのカスタムメモリアロケーターへの切り替えが含まれます(C++では合理的に簡単ですが、Java and Python通常はありません)。
OPと同じ問題を扱っています。これまでのところ、それはtcmallocで可能であるようです。私は2つの解決策を見つけました:
tcmallocをリンクしてプログラムをコンパイルし、次のように起動します。
env TCMALLOC_RELEASE=100 ./my_pthread_soft
ドキュメント は、
妥当なレートは[0,10]の範囲です。
しかし、10では十分ではないようです(つまり、変化はありません)。
すべての解放されたメモリを解放するのが興味深いコードのどこかを見つけて、このコードを追加します。
#include "google/malloc_extension_c.h" // C include
#include "google/malloc_extension.h" // C++ include
/* ... */
MallocExtension_ReleaseFreeMemory();
2番目の解決策は私の場合非常に効果的です。最初のものは素晴らしいですが、あまり成功していません。たとえば、正しい数を見つけるのは複雑です。
私のアプリにも同様の問題があり、調査の結果、割り当てられたオブジェクトが小さい(私の場合は120バイト未満)場合、何らかの理由でglibcがシステムにメモリを返さないことに気付きました。
このコードを見てください:
_#include <list>
#include <malloc.h>
template<size_t s> class x{char x[s];};
int main(int argc,char** argv){
typedef x<100> X;
std::list<X> lx;
for(size_t i = 0; i < 500000;++i){
lx.Push_back(X());
}
lx.clear();
malloc_stats();
return 0;
}
_
プログラム出力:
_Arena 0:
system bytes = 64069632
in use bytes = 0
Total (incl. mmap):
system bytes = 64069632
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0
_
約64 MBはシステムに戻りません。 typedefを次のように変更すると、_typedef x<110> X;
_プログラムの出力は次のようになります。
_Arena 0:
system bytes = 135168
in use bytes = 0
Total (incl. mmap):
system bytes = 135168
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0
_
ほとんどすべてのメモリが解放されました。どちらの場合でもmalloc_trim(0)
を使用すると、システムにメモリが解放されることにも気付きました。
これは、上記のコードに_malloc_trim
_を追加した後の出力です。
_Arena 0:
system bytes = 4096
in use bytes = 0
Total (incl. mmap):
system bytes = 4096
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0
_
あなたが言及したものを含むすべての「通常の」mallocについて、メモリは解放されてプロセスによって再利用されますが、システム全体には戻されません。システム全体への解放は、プロセスが最終的に終了したときにのみ発生します。
あなたがリストするものの中で、Hoardだけがメモリをシステムに返します...しかし、それが実際にできる場合、それはプログラムの割り当て動作に大きく依存します。
短い答え:mallocサブシステムがメモリをOSに戻すように強制するには、malloc_trim()を使用します。それ以外の場合、メモリを返す動作は実装に依存します。
FreeBSD 12のmalloc(3)
は jemalloc 5.1を使用し、解放されたメモリ(「ダーティページ」)をmadvise(...MADV_FREE)
を使用してOSに返します。
解放されたメモリは、opt.dirty_decay_ms
およびopt.muzzy_decay_ms
によって制御される時間遅延の後にのみ返されます。詳細については、 manual page およびこの decayベースの未使用のダーティページのパージの実装に関する問題 を参照してください。
以前のバージョンのFreeBSDは、以前のバージョンのjemallocとともに出荷されました。これも解放されたメモリを返しますが、パージする内容とタイミングを決定するために別のアルゴリズムを使用します。