計算機でmapと言うと、リストの要素を無名関数で一括処理することや(Python)、ハッシュテーブルのような構造(Java)を思い浮かべる人が多いかも知れませんが、mmapはファイルに仮想メモリーを割り当てるOSの提供するAPIです。巨大データを処理したり、高速ファイルアクセスが必要なときに使われます。
主記憶利用量の削減
Rでもmmapパッケージを用いると、mmapを利用することができます。取扱説明書(Vignettes)の利用例をそのまま説明に使います。まず、
library(mmap) r <- rnorm(20e6)
とベクトルをつくりましょう。
gc(verbose = TRUE)
とガーベッジ・コレクターを走らせると、メモリー利用量が分かります。

このベクトル r を、mmapオブジェクトの r に変更します。
r <- as.mmap(r)
もう一度、ガーベッジ・コレクターを走らせて、メモリー利用量を確認しましょう。

157.2MB使っていたVcellsが、4.6MBの利用に減りました。減った、一時ファイルに退避されています。
mmapオブジェクトへのアクセス方法
残念なことにmmapオブジェクトは透過的には利用できないです。以下は標準のベクトルへの操作と同じですが、
r[1:10] r[87643] head(r) tail(r) length(r) r[1:10] <- 1:10 r[87643] <- pi head(r) r[87643]
getElementを通して制御しているため、合計や平均の参照は以下のようにします。
sum(r[]) # sum(r)はエラー mean(r[]) # mean(r)はエラー
後述の例で使いますが、構造体のmmapオブジェクトの場合はr[]$x,r[123]$xのようにアクセスするので、一般のRユーザーには非直感的だと思います。
仮想メモリーの解放
利用後、mmapオブジェクトはmunmap関数で開放します。
munmap(r)
開放してもファイルは残るのですが、ここでは一時ファイル領域につくられているのでOSの再起動時などに消去されます。
既存ファイルを仮想メモリーとして使う
主記憶領域の拡張としてではなく、永続的な保存領域として活用することもできます。
データを生成して仮想メモリーに保存する
仮想メモリーとして使われるファイルの中身はシンプルなバイナリ・データなので、R以外で作成したものを取り出してもよいのですが、Rの乱数生成したデータを保存してみましょう。
library(mmap) # ファイル名は保存できれば何でもよい fname <- paste(Sys.getenv("HOME"), "mmap.dat", sep="/") # レコード型(データ構造)をmmap::structを使って宣言 record.type <- struct(y = double(), x = double(), z = double()) # レコード数 n <- 100 # レコード型のサイズ×レコードの数のraw型のmmapを作り、即開放 munmap(as.mmap(raw(nbytes(record.type)*n), mode = raw(), file = fname)) # レコード型を指定してファイルを仮想メモリに割り当てなおす r <- mmap(fname, mode = record.type) # 記録する r[, "x"] <- runif(n) r[, "z"] <- runif(n) r[, "y"] <- 1 + r[,]$x - r[,]$z + rnorm(n) # mmapを開放、削除 munmap(r); rm(r)
これでユーザーフォルダにmmap.datができました。なお、レコード型のデータ構造は browseVignettes("mmap") で見られる Vignette の表を参照してください。
既存ファイルを仮想メモリーにする
ファイル名とレコード型が分かっていたら、仮想メモリーに割り当てて再利用できます。
# record.type <- struct(y = double(), x = double(), z = double()) # 再度、ファイルを仮想メモリーに割り当てる s <- mmap(fname, mode = record.type) # 最尤法をかけてみる r_nlm <- nlm(function(p){ if(p[1]<=0) return(Inf) -sum(dnorm(s[]$y - p[2] - p[3]*s[]$x - p[4]*s[]$z, sd = p[1], log = TRUE)) }, c(1, 0, 0, 0)) # 仮想メモリーを解放する munmap(s) r_nlm$estimate # 誤差項の標準偏差,切片項、xの係数、zの係数がだいたいDSG通りに出るハズ
lmやglmのような内部的にmodel.matrixを使う関数は、仮想メモリー領域のデータから同じ長さの行列を作ってから推定を行うことになり、mmapの御利益が乏しいので最尤法にしました。
データ構造体へのアクセスの癖
s$yとアクセスできずs[]$yとアクセスすることは上の通りですが、同様にs[2]$yとアクセスするのであって、s$y[2]とアクセスできなかったりします。
C言語の構造体を保存したファイル
ありそうな応用例で、C言語の構造体を保存したファイルを仮想メモリーに割り当てる例を示します。
Cで構造体を保存
R側でパディングを処理するのが手間なので、構造体のアライメントは無効(というか1バイト単位)にしておきましょう。
#include<stdlib.h> #include<stdio.h> #define N 100 #pragma pack (push, 1) typedef struct { unsigned int a; double b; } SD; #pragma pack (pop) int main(){ SD *v; unsigned i; FILE *os; unsigned char *fname = "mmap_c.dat"; unsigned n = N; v = malloc(sizeof(SD)*N); for(i = 0; i < N; i++){ v[i].a = i; v[i].b = 1.23; } if(NULL == (os = fopen(fname, "wb"))){ fprintf(stderr, "Couldn't open the file: %s\n", fname); return -1; } fwrite(v, sizeof(SD), N, os); fclose(os); free(v); return 0; }