R の計算は他のプログラミング言語に比べてやや遅いことが知られている。 しかし、for 構文や apply 関数などのループの使用を避け、 組み込み関数などを積極的に使えば、計算もある程度高速化できる。 中ではどうしてもループを使わなければならないときは、 並列化処理を行うことで計算を高速化できる。 R で並列化処理を行うには parallel パッケージを使う。
parallel パッケージ
R をインストールすると、parallel パッケージもデフォルトでインストールされる。 library 関数を使って parallel パッケージを呼び出して使える状態にする。
library(parallel)
現在使用しているコンピュータで利用可能なコア数を調べるには、 parallel パッケージの detectCores
関数を利用する。 detectCores
関数をデフォルトのままで利用すると理論的なコア数が出力される。 logical = FALSE
オプションを付け加えることで、物理的なコア数が出力される。
detectCores()
## [1] 8
detectCores(logical = FALSE)
## [1] 4
現在の技術では、物理的な CPU 1 つを、2 つあるかのように見せかけて使用ことができる。 そのため、一般的に、理論コア数は物理コア数よりも多い。 コンピュータに詳しくなければ、理論コア数を参考にしてコア数設定すれば良い。
parallel パッケージでは、forking と socket の 2 種類の並列化方法をサポートしている。 forking は、現在実行中の R 環境全体を指定されたコアにコピーし、 そのコピーされた環境を使用して計算を分担して処理する方法である。 この方法は、R 環境全体をコピーしてから並列計算を行うため、 並列計算を行う直前までに使ったパッケージ、変数や関数も、 そのまま並列計算の中でも利用できる。 ただし、forking は UNIX システムのプロセス管理機能を使用しているため、 Windows ではこの機能を利用できない。
soket は、指定された数の分だけ R を起動して、 新しく立ち上げた R 環境で計算を分担して処理する方法である。 R を新しく立ち上げているため、並列計算直前までに使っていたパッケージや変数などの情報がない。 そのため、並列計算のときに使用したい変数や関数を新しく立ち上げた R にコピーしたり、 パッケージを呼び出したりする作業を行う必要がある。
Forking (mclapply 関数)
Forking を利用した並列計算は、mclapply
関数を使用する。 mclapply
関数は基本的に R の apply
の使い方と同じである。 ここで mclapply
関数で 2 コア分使用して、 一様分布の最大値と最小値の差を 10000 回計算してみることにする。
d <- mclapply(1:10000, function(i) {
x <- runif(10)
max(x) - min(x)
}, mc.cores = 2)
次に、並列処理の中で、自分で定義した関数を使う例を示す。 例えば、一様分布から生成された乱数を独自の関数で対数化した後に最大値と最小値の差を並列計算で求めてみる。
f <- function(x) { log(x) }
d <- mclapply(1:10000, function(i) {
x <- runif(10)
x <- f(x)
max(x) - min(x)
}, mc.cores = 2)
上で見たように、並列計算の前に定義した関数 f
を mclapply
関数の中で直接呼び出して使用することができる。
Socket (par*apply 関数)
socket を利用した並列化では、 コア数を自分で確保し、それぞれのコア上に必要なパッケージ、変数や関数をコピーしておく必要がある。 まずは、並列計算の中で独自で定義した関数の呼び出しを行わない例を見てみる。 コアの確保は makeCluster
関数を使い、並列計算は parLapply
関数を使う。 計算が終了した後に、確保したコアを解放するために stopCluster
関数を実行する。
cl <- makeCluster(2)
d <- parLapply(cl, 1:10000, function(i) {
x <- runif(10)
max(x) - min(x)
})
stopCluster(cl)
parLapply
関数のほかに、parSapply
などの apply ファミリーに対応した関数を使うことができる。
次に、独自で定義した関数や変数を並列計算の中で使う例を示す。 このとき、並列計算(parLapply
関数)の中で使いたい関数あるいは変数がある場合、 それらの関数および変数を cluserExport
関数でコピーしておく必要がある。 また、並列計算にパッケージや前処理などが必要な場合、 clusterEvalQ
関数を使用してパッケージの呼び出しや前処理用を行う必要がある。
f <- function(x) { log(x) }
cl <- makeCluster(2)
clusterEvalQ(cl, {
library(MASS)
k <- 10
})
## [[1]]
## [1] 10
##
## [[2]]
## [1] 10
clusterExport(cl, 'f')
d <- parLapply(cl, 1:10000, function(i) {
x <- runif(10)
x <- f(x) * k
max(x) - min(x)
})
stopCluster(cl)