RでdoMCを使ったお手軽並列計算

前書き
奥様1「ちょっと奥さん!最近のCPUってマルチコアになってて何でも並列計算とかできるらしいわよ?」
奥様2「え…でも並列計算の計算環境作成ってすごく面倒くさいんでしょう?」
マーク・パンサー「なんで?面倒くさくないよ?」
奥様2「え…でも大して速くならないんでしょ?」
マーク・パンサー「違うよ,全然違うよ」

ということでRでの並列計算のお話です.
以前にも
LAM/MPIのインストール for MacOSX
とかいう記事を書きましたが,正直このアプローチは面倒くさすぎるというか設定した私ももう一度やれと言われたら多分無理.
基本的に以前の記事はネットワーク経由での並列計算でしたが,今回はマシン単体での並列計算のお話.
つまり「近頃の若いマシンはマルチコアとかいう大層なものを積んでるんだから,精一杯こき使ってやろうぜい」というお話です.





なんで並列計算やる必要あるの?
さて,まずRでの並列計算についてですが,まずRの欠点として…
・基本的にオンメモリでデータを保持
・マルチCPU環境でも基本的に1コアで計算
・デフォルトで大規模データの処理が苦手(デフォルトでは2^31-1個までしか保持てきない)
など割と言い始めたらキリがないですが,今日のお話は2番目の「マルチCPU環境でも基本的に1コアで計算」です.
Rは基本的に1コアで計算してしまうので,並列計算をしてあげないと計算資源の無駄なわけです.



並列計算のパッケージっていろいろあるけど,どのパッケージを使えばいいの?
さて,Rで並列計算するには,まずどのパッケージを使えばいいの?という話が挙がります.
ちょっと調べただけでもdoNWS・doSNOW・doSMP・doMC・rMPIなど結構出てきます.
この中で最適なパッケージはどれかというと…ココの記事いわく…
http://www.revolutionanalytics.com/subscriptions/docs/RevolutionREnterprise4.0/parRman.pdfの22ページより…

The doNWS and doSNOW packages are parallel backends for clusters of workstations; they can be used on a single multi-core or multi-processor computer, but they are not optimized for such use.
The doSMP and doMC packages are parallel backends for foreach that are intended for parallel processing on a single computer with multiple cores or processors. The doSMP package is available on all platforms, while doMC, which depends on the multicore packages, is currently available only on Mac and Linux systems.
なので
doNWS・doSNOW: マルチコアシステムには最適化されていない
doSMP・doMC: マルチコアシステムに最適化されている
→というわけで、一台のマシンで使う場合はdoSMP・doMCが最適

さて,ここからは個人的な経験談なのですが,Mac版doSMPは経験上どうもうまく動かない傾向にあるので、並列計算を行うにあたってはdoMCを使うのが無難かなぁと思います.
(追記:今さら気づきましたがdoMCはLinuxかMac版しか無い模様.Windowsユーザの方はdoSMPを使うしかないようです…といってもコードの書き方は殆ど変わらなかったはず.)




doMCをインストールしよう
doMCのインストールは簡単.
セオリー通り
install.packages("doMC")
とやるだけです.依存パッケージも同時にインストールしてくれます.



doMCを使ってみよう
まずは参考までにサンプルコードを貼っておきます.とっても簡単です.
何のことはない,N個の正規乱数を発生させて,その和を取るということを1000回くりかえした計算時間を返すという単純なものです.
#計算コア数ベクトル生成(コア数: 1,2,4,6,8)
n_core <- c(1,seq(2,8,by=2))
#正規乱数発生数を設定
N <- 10^6
#計算時間の結果を突っ込む空マトリックスの作成
result.mat <- matrix(0,length(n_core),3)
for(i in 1:length(n_core)){
    registerDoMC(n_core[i])
    result <- system.time(foreach(i = 1:1000,.combine = "cbind") %dopar% {
        sum(rnorm(N))
    })
    core.d <- c(n_core[i],result[3])
    names(core.d) <- c("Cores","ProcTime")
    print(core.d)
    result.mat[i,] <- as.numeric(result)[1:3]
}
return(result.mat)
と言った感じです.ね?簡単でしょ?
普段のコードと違うところと言ったら
registerDoMC(n_core[i])

foreach(i = 1:1000,.combine = "cbind") %dopar% {hogehoge}
の所ぐらいです.

さて,それぞれの関数の意味ですが…
registerDoMC(hoge)
で使用するコア数の設定をすることができます,
例えばCore2DuoだったらregisterDoMC(2),Corei7だったら…これはCPUによって違いますね.
次に
result <- foreach(i = 1:1000,.combine = "cbind") %dopar% {hogehoge}
に並列計算の処理を書きます.
基本的にhogehogeの部分に書く処理内容はfor文と同じですが,結果がresultに保存されるので,その点だけは注意です.
つまり
result <- c()
foreach(i = 1:1000,.combine = "cbind") %dopar% {
    result[i] <- mean(rnorm(10000))
    }
とか書いてもダメで,
result <- foreach(i = 1:1000,.combine = "cbind") %dopar% {
    mean(rnorm(10000))
    }
と書いてくださいといいうことです.
返り値はいろいろな形が設定できます.詳しくはヘルプを見てね!って感じですが,cbindにしておけばベクトルで返してくれますし,デフォルトだとそれぞれの計算結果をリストで返してくれたはずです.
注意する点はこれぐらいでしょうか.
これでとっても簡単に計算ができます.




実際どれぐらい早くなるの?
ここまでの記事は正直ほかのサイトさんでも書かれてるので大して新しい情報は無いのですが,実際の効果についてはあまり触れられていないので,記入しておこうかなと思います.
以前機会があって結構大規模なマシンに触れる機会があったので,試しにテストして見ました.

・テスト内容
100,000個の標準正規乱数を1,000回発生させて,それぞれの和をとる計算にかかる時間を計測

・マシン
R for Linux 64bit (Ubuntu Linux 11.04)
CPU: Xeon 2.8GHz *2 (24 core)
RAM: 48GB



R for Mac OS X 64bit (Mac OS X 10.7.1)
CPU: Core i7 2.3GHz *1 (8 core)
RAM: 8GB
の2台で計測

結果
赤線がXeonのマシン,青線がCore i7のマシンになっています.
計算時間については1コア時の計算時間を1として正規化しています.
計算の内容にも依るのかなと思いますが,どちらも実コア数近辺までは計算速度が早くなっている気がします.
それ以降では計算効率がガクッと落ちてしまっています.
registerDoMC関数で指定するコア数は,実コア数までにしておくのが妥当かもしれません.

以上,これでみなさんもハッピー並列計算ライフをお楽しみください.