ちょっと前から groonga を使うとプロセスサイズが肥大化するのが気になっていて、メモリ関係を色々調べていたのですが、そこでわかったことなどを書いときます。
malloc() しただけではOSのメモリは使用されない
メモリを1GB獲得するだけのこんなプログラムを作って実行してみます。
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char *p; char buf[1024]; int i; p = malloc(1024*1024*1024); gets(buf); for (i=0; i<1024*1024; i++) memcpy(p+i*1024, buf, sizeof(buf)); pause(); }
実行前:
% free total used free shared buffers cached Mem: 4043524 2207776 1835748 0 139028 1603780 -/+ buffers/cache: 464968 3578556 Swap: 1951860 0 1951860
実行中:
% free total used free shared buffers cached Mem: 4043524 2207804 1835720 0 139044 1603348 -/+ buffers/cache: 465412 3578112 Swap: 1951860 0 1951860 % ps up 3316 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND tommy 3316 0.0 0.0 1050268 248 pts/2 S+ 23:14 0:00 ./malloc
プロセスサイズが 1GB 超えてるのに OS の空きメモリはほとんど変化ありません。
プログラムを実行した端末でRETURNキーを押すと獲得したメモリに書き込みを行います。
その後の状態:
% free total used free shared buffers cached Mem: 4043524 3258232 785292 0 139044 1603296 -/+ buffers/cache: 1515892 2527632 Swap: 1951860 0 1951860 % ps up 3316 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND tommy 3316 8.4 25.9 1050268 1048852 pts/2 S+ 23:14 0:01 ./malloc
メモリ使用量が増えました。
Linux で使用するまで実際にメモリが獲得されないのは memory overcommit という仕組みらしいです。OS の仮想メモリを超える分のメモリを malloc() で獲得することも可能のようです。
malloc() した領域をいざ使用しようとした時に、仮想メモリが不足していてメモリ獲得できなかった場合はどうなるかというと、なにかしらのプロセスが SIGKILL で殺されるようです(OOM Killer)。
これはちょっとひどい仕組みのような気がします。Solaris はたしか仮想メモリを超えるメモリの獲得は失敗すると思いました(うろ覚え)。
Linux 2.6 では sysctl で vm.overcommit_memory を変更すればこの振る舞いを変更できるようです。
mmap() もOSのメモリは使用されない
mmap() でファイルをプロセスのメモリにマッピングしてみます。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char *p; char buf[1024]; int i; int fd; fd = open("/tmp/1GB", O_RDWR); p = mmap(0, 1024*1024*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); gets(buf); for (i=0; i<1024*1024; i++) memcpy(p+i*1024, buf, sizeof(buf)); pause(); }
実行前:
% free total used free shared buffers cached Mem: 4043524 2237048 1806476 0 148092 1613264 -/+ buffers/cache: 475692 3567832 Swap: 1951860 0 1951860
実行中:
% free total used free shared buffers cached Mem: 4043524 2237136 1806388 0 148100 1613016 -/+ buffers/cache: 476020 3567504 Swap: 1951860 0 1951860 % ps up 3435 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND tommy 3435 0.0 0.0 1050264 248 pts/2 S+ 00:30 0:00 ./mmap
malloc() 時と同じく、プロセスサイズは増えましたが、OSの使用メモリは増えません。
書き込み後:
% free total used free shared buffers cached Mem: 4043524 2238348 1805176 0 148184 1611888 -/+ buffers/cache: 478276 3565248 Swap: 1951860 0 1951860 % ps up 3435 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND tommy 3435 3.0 25.9 1050264 1048844 pts/2 S+ 00:30 0:01 ./mmap
書き込み後も OS の使用メモリは増えてません。mmap() はファイルをメモリにマッピングするので、メモリに書き込んでもファイルに書き込むことになるからだと思います。
procfsで確認
malloc() と mmap() のどちらによってプロセスのメモリサイズが大きくなっているかは、/proc/
malloc()の場合:
% grep ^Vm /proc/3463/status VmPeak: 1050268 kB VmSize: 1050268 kB VmLck: 0 kB VmHWM: 248 kB VmRSS: 248 kB VmData: 1048612 kB VmStk: 136 kB VmExe: 4 kB VmLib: 1484 kB VmPTE: 20 kB VmSwap: 0 kB
mmap()の場合:
% grep ^Vm /proc/3455/status VmPeak: 1050264 kB VmSize: 1050264 kB VmLck: 0 kB VmHWM: 248 kB VmRSS: 248 kB VmData: 32 kB VmStk: 136 kB VmExe: 4 kB VmLib: 1484 kB VmPTE: 16 kB VmSwap: 0 kB
malloc() で獲得したメモリは VmData に表れますが、mmap() の場合は VmData は小さいままです。
groongaのプロセスサイズが大きくても気にするな
で、groonga ですが Twitter で @tasukuchan に教えてもらいました。
ファイルをたくさんmmapしているので、VIRTは増えちゃいますね。リークっぽく見えちゃうのも分かります。64bit専用としているのも、そのためです。
http://twitter.com/tasukuchan/status/27121532855
ということで groonga のプロセスサイズが大きいのは気にしなくても良さそうです。
実は groonga が無駄にメモリを消費しているのではないかと疑っていたのでした。すいません…。
mmap() のことはすっかり頭から抜けてました。最近は Ruby ばっかりで C でプログラム書くことはほとんどないので、だんだんダメになっていってるようです…。