2021/09/28

Rの消せないメモリ領域

 Rがメモリを食いすぎる問題について調査をしていて、一定の傾向が見えたので、備忘録。

 

現象は、あるオブジェクト a について

  •  object.size(a) はそんなに大きくない。200MB程度。
  • put(a, file = "a.dat") したデータを a <- dget("a.dat") すると、プロセスのメモリ使用量が5GB程度に膨れ上がる。
  • その後 rm(a) や rm(list = ls()) したり、gc(T,T) を何度かしたりしても、プロセスのメモリ使用量が 5GB から減らない。

 例えば、b <- runif(n = 1000000000) などすると、プロセスのメモリ使用量もオブジェクトサイズも a より大きくなりますが、rm(b); gc(T,T); で元に戻ります。

しかし、上のaの場合、その後何をやってもプロセスのメモリ使用量が減らず、メモリに関する様々な不具合が待っています。これは地味に辛い。


結論というか、いろいろ外形をつついてみて、およそそういうことなのでは?と思ったのが、ファイル読み込み時のバッファ領域なのではないか仮説です。

実は object a はかなり複雑なオブジェクトで、大小様々な list の入れ子になっていまして、2000 x 2000 ほどの double の行列が入っていたり、1500個くらいの二次元ベクトルが list で入っていたりと、行も列も異なるデータがいろんな深さで入っていました。

推測ですが、R がそれを dget で読み込むにあたって、オブジェクトの中の一つひとつのアイテムに対し、例えば最大の大きさの領域を確保しているのではないか。つまり、一番大きいアイテムが 2000x2000 の行列だとして、一番小さいアイテムがdouble 1個とすると、double 1個を読み込むためにも 2000x2000 の行列を読み込むのと同じ大きさのバッファを用意するのではないか。そして、gc ではそれをコントロールできなくて、結局巨大なメモリを確保したままのプロセスが残ってしまうのではないか。

これを本当に調べるには dget の実装を見てみればいいのですが、そこまで余裕がないので今日は諦めます。

 

解決方法ですが、ファイル読み込みのバッファが問題ならば、それを作らなければよい、ということで、dput/dget ではなく、save/load を使うと見事にメモリ使用量が減りました。それでもすこし入力バッファっぽい領域(gcで消えない領域)は残ったのですが、dget に比べて 1/10 程度です。

なお、dump/source は dput/dget と挙動が同じなので、テキスト形式でオブジェクトを保存したい場合はこれしか方法が無さそう。オブジェクトが単純なら、write.table や write.csv がよいかも。

 

あとは、ファイル読み込みバッファをリセットするような関数があればそれを使って確保領域をリリースすることもできると思うのですが、見つけられていません。