glibc ヒープ領域の縮退
概要
mysqlコマンドで大量のレコードを取得中に、psコマンドでメモリの使用量を見ていたらクエリ実行中はVSZ(仮想アドレス空間のサイズ)がじわじわと増え続けていたが、クエリの実行が完了するとVSZのサイズが小さくなった。
(だいぶ前の)libcではmalloc()してヒープ領域が拡張されたら、free()してもバッファがFreeListに返されるだけで、ヒープ領域はそのままであったため、VSZのサイズが小さくなることはなかったと思ったが、最近ではちゃんとヒープ領域を縮退してくれるらしい。少し調査。
glibcのソース
glibc-2.4.tar.gzのソースを確認してみる。詳しくは追っていないが、以下のような流れで縮退処理に行っている。grow_heap()にはマイナスの引数を渡して縮退させている。
free() => public_fREe() => _int_free() => heap_trim() => grow_heap()
実際の挙動
実際にmalloc()&free()してプロセスのアドレス空間のサイズの動きを見てみた。以下のソースは1kbのメモリを20k個確保(合計20M)して解放する。(使用したライブラリはglibc 2.3.5)
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #define BUFFCNT (20*1024) #define BUFFSIZE (1024) void *buffp[BUFFCNT]; main() { void *p; int i; for (i = 0 ; i < BUFFCNT ; i++) { if (!(buffp[i] = malloc(BUFFSIZE))) return -1; } sleep(5); printf("free\n"); for (i = 0 ; i < BUFFCNT; i++) free(buffp[i]); while (1) { sleep(1); } }このプログラムを実行してps -auxでVSZの変化を見てみると、malloc後はVSZは20M+となり、freeしたらVSZが小さくなって、ヒープ領域がちゃんと縮退されているのがわかる。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND malloc後 tomita 5759 0.6 2.1 22216 21048 pts/9 S+ 14:35 0:00 ./a.out free後 tomita 5759 0.6 0.0 1628 572 pts/9 S+ 14:35 0:00 ./a.outまた、以下のようにして末尾のバッファをfreeしないと、ヒープ領域の末尾にバッファが残るので縮退はされない。
for (i = 0 ; i < BUFFCNT - 1; i++)
free(buffp[i]);