並列計算(snowfall)

マルチコアが主流となってきているので、並列計算のパッケージである「snowfall」を紹介します。
なお、特に断りが無い場合はSocketクラスターを使用しています。

現在編集中


目次


並列計算で計算速度は向上するか?

並列化できる割合によって、理論上の限界が変わってきます。
アムダールの法則を参照)
なお、かえって遅くなる場合もあります。
並列化の概要は、このページ後述の並列計算事始めを参照してください。

対象者

計算速度を向上させたいが、煩わしいことはなるべく避けたい。
そのような方にとって、並列計算パッケージ「snowfall」は一つの方法かもしれません。

本記事は、以下の方を想定しています。

レベルとしては以下の方

高速化の手段はいくつかありますので、適切な方法を用いてください。
CRAN Task View: High-Performance and Parallel Computing with R

snowfallの利点

とりあえず動かす

  1. ライブラリーの読み込み
  2. sfInitでクラスターを準備する。
  3. (必要があれば)ワーカーに変数やオブジェクトを渡す。
  4. snowfall 関数を使用して並列計算を行う。
  5. クラスターを止める
    > library(snowfall)
     要求されたパッケージ snow をロード中です 
    > sfInit(parallel=TRUE, cpus=4)
    R Version:  R version 3.2.2 (2015-08-14) 
    
    snowfall 1.84-6 initialized (using snow 0.3-13): parallel execution on 4 CPUs.
    
    > a <- 1:10
    > sfExport("a")
    > sfSapply(11:20, function(x){x + sum(a)})
     [1] 66 67 68 69 70 71 72 73 74 75
    
    > #検証
    > sapply(11:20, function(x){x + sum(a)})
     [1] 66 67 68 69 70 71 72 73 74 75

代表的な関数 対応表

basesnowsnowfallコメント
applyparApplysfApply
lapplyparLapplysfLapply
sapplyparSapplysfSapply
apply(X, MARGIN=1, FUN, ...)parRapplysfRapply
未実装(Version 1.84-4)
snowfallでは関数は定義されているが、以下のとおり。
sfRapply does not exists yet. Use Snow's parRapply instead.

sfApplyで代用する。
apply(X, MARGIN=2, FUN, ...)parCapplysfCapply
未実装(Version 1.84-4)
snowfallでは関数は定義されているが、以下のとおり。
sfCapply does not exists yet. Use Snow's parCapply instead.

sfApplyで代用する。
mapplyclusterMapsfClusterMap
未実装(Version 1.84-4)
snowfallでは関数は定義されているが、以下のとおり。
Currently no wrapper for clusterMap

snowfallの関数では代用できないため、並列化をする場合はsnowパッケージを使用する。
%*%parMMsfMM

snow snowfall 各種関数 対応表

カテゴリーは主観で分けています。

カテゴリーsnowsnowfallコメント
クラスター作成makeClustersfInitすべてsfInitで行えます。
makeSOCKcluster
makePVMcluster
makeMPIcluster
makeNWScluster
オブジェクトなどの引渡しclusterExportsfExport各クラスターに特定のオブジェクトをエクスポートします。
sfExportAll各クラスターに全てのオブジェクトをエクスポートします。
sfRemove各クラスターから特定のオブジェクトを削除します。
sfRemoveAll各クラスターから全てのオブジェクトを削除します。
sfLibrary各クラスターにライブラリーを読み込ませます。
sfSource各クラスターにソースファイルを読み込ませます。
並列基本関数parApplysfApply
parLapplysfLapply
parSapplysfSapply
parRapplysfRapply未実装(Version 1.84-4)
parCapplysfCapply未実装(Version 1.84-4)
parMMsfMM%*%
clusterMapsfClusterMap未実装(Version 1.84-4)
その他並列関数clusterApplysfClusterApplysfClusterApplyはクラスター数以下の要素しか受け取れない。
(使えない関数の気がします。)
clusterApplyLBsfClusterApplyLBclusterApplyでは、1要素の計算が終わるまですべてのクラスターが待機していましたが
これは1要素の計算が終了次第次の計算に移ります。
(Load Balanced version of sfClusterApply)
sfClusterApplySR1要素の計算が終了するごとにsaveを行い、予期せぬ終了(サーバーメンテナンスなど)があった場合でも、続きから計算を始めることが出来ます。
(ただし、saveを頻繁に行うので遅くなるようです。)
(Saves intermediate Results)
clusterCallsfClusterCall各クラスターに同一の命令文を実行させます。
clusterEvalQsfClusterEval各クラスターに同一の未評価の命令文を実行させます。
乱数clusterSetupRNGsfClusterSetupRNG乱数の生成をします。
(set.seedのようにNULLによって乱数の生成が出来ないため、少々扱いにくいです。)
clusterSetupRNGstreamsfClusterSetupRNGstream
clusterSetupSPRNGsfClusterSetupSPRNG
クラスターの終了stopClustersfStop
クラスターの使用状況snow.timeクラスターの使用状況を調査出来る関数です。
printprintを拡張し、snow.timeで取得したクラスターの使用状況表示します。
plotplotを拡張し、snow.timeで取得したクラスターの使用状況表示します。
デバッグsfCat各クラスターにcat文を実行させます。
各種情報等clusterSplitsfClusterSplithelpを参照の事。
setDefaultClusterOptions
getMPIcluster
sfParallel
sfIsRunning
sfCpus
sfNodes
sfGetCluster
sfType
sfSession
sfSocketHosts
sfSetMaxCPUs
sfRestore

どの並列関数を使用するべきか

snow.time でクラスターの使用状況を探る

関数snow.timeを使用することによって、各クラスターの使用状況を確認できます。

> st.hoge <- snow.time(hoge)
> st.hoge
elapsed    send receive  node 1  node 2  node 3  node 4 
  21.75    0.00    0.02   13.77   12.24   14.50   12.00 

snow.timeで得られた結果は、plotで詳細に状況を確認できます。

> plot(st.hoge)

applyとclusterApply(snow)

applyでの実行時間が53.03秒に対して、並列計算を行ったclusterApplyは21.76秒!
何と2倍以上の高速化が実現!素晴らしい!
&ref(): File not found: "fig01_apply.jpg" at page "並列計算(snowfall)";&ref(): File not found: "fig01_clusterApply.jpg" at page "並列計算(snowfall)";

clusterApply(snow)とclusterApplyLB(snow)

clusterApplyは、各クラスターに1個引数を渡し、すべてのクラスターの計算終了を待って次の引数をクラスターに渡します。
そのため、実行時間に差がある場合は待機状態になるクラスターが発生してしまいます。
clusterApplyは確かに速くなっているが、クラスターの待機時間があるのでこれを減らすようにしたい。
そこで出てきたのが、clusterApplyLBです。
clusterApplyLBは、計算が終了したら次の引数を引き渡すので、待機状態になるクラスターが(ほとんど)ありません!
&ref(): File not found: "fig01_clusterApply.jpg" at page "並列計算(snowfall)";&ref(): File not found: "fig02_clusterApplyLB.jpg" at page "並列計算(snowfall)";

引数にクラスターを指定しないsnowfall

snowでは全ての並列関数にクラスターを指定しなくてはなりません。

clusterApply(cl, X, MARGIN, FUN, ...)

これでは、まず通常処理でテストを行った後に、並列化するときは
まずapplyでテストを行った後、該当部分をすべてparApplyに書き換えなくてはならず手間がかかります。

そこで登場するのがsnowfallです。

sfApply(x, margin, fun, ...) 
  apply(X, MARGIN, FUN, ...) 

ほら、一緒。
これは非常にありがたい!並列化を意識せずに使用できます!

しかも、snowfallの並列関数は通常処理の場合、通常の関数を「自動的に」適用してくれます。

> sfApply
function (x, margin, fun, ...) 
{
    sfCheck()
    checkFunction(fun)
    if (sfParallel()) 
        return(parApply(sfGetCluster(), x, margin, fun, ...))
    else return(apply(x, margin, fun, ...))
}
<environment: namespace:snowfall>

テストを行った後に、sfInitで並列化をし、改めて実行すると並列計算になる。
何と便利なパッケージ!

sfClusterApplyLBとsfLapply

少し横道にそれましたが、関数の選択に戻ります。

並列関数の異端児 sfClusterApplySR

結局どの関数を使えばいいの?

未実装関数について

sfRapply

sfCapply

sfClusterMap

Tips

sfApplyでベクトルを引数に取る場合使用時の注意

通常処理と並列処理の切り替え

R起動時のsnowfallの設定

乱数の使用

クラスターの種類

クラスターコメント
SOCKデフォルト。
追加パッケージ不要。ワークステーションやPCおよびノートPCに適する。
MPIパッケージRmpiが必要。
PVMパッケージrpvmが必要。
NWS追加パッケージ不要。

並列計算を中断した場合

並列計算を中断した場合は、必ずworkerをsfStop()で一度終了させてください。
終了しないと、意図としない結果が返ってきます。

> #普通に実行
> sfSapply((1:200)/1000, function(x){Sys.sleep(x); x})
  [1] 0.001 0.002 0.003 0.004 0.005 0.006 0.007 0.008 0.009 0.010 0.011 0.012 0.013 0.014 0.015 0.016 0.017
 [18] 0.018 0.019 0.020 0.021 0.022 0.023 0.024 0.025 0.026 0.027 0.028 0.029 0.030 0.031 0.032 0.033 0.034
 [35] 0.035 0.036 0.037 0.038 0.039 0.040 0.041 0.042 0.043 0.044 0.045 0.046 0.047 0.048 0.049 0.050 0.051
 [52] 0.052 0.053 0.054 0.055 0.056 0.057 0.058 0.059 0.060 0.061 0.062 0.063 0.064 0.065 0.066 0.067 0.068
 [69] 0.069 0.070 0.071 0.072 0.073 0.074 0.075 0.076 0.077 0.078 0.079 0.080 0.081 0.082 0.083 0.084 0.085
 [86] 0.086 0.087 0.088 0.089 0.090 0.091 0.092 0.093 0.094 0.095 0.096 0.097 0.098 0.099 0.100 0.101 0.102
[103] 0.103 0.104 0.105 0.106 0.107 0.108 0.109 0.110 0.111 0.112 0.113 0.114 0.115 0.116 0.117 0.118 0.119
[120] 0.120 0.121 0.122 0.123 0.124 0.125 0.126 0.127 0.128 0.129 0.130 0.131 0.132 0.133 0.134 0.135 0.136
[137] 0.137 0.138 0.139 0.140 0.141 0.142 0.143 0.144 0.145 0.146 0.147 0.148 0.149 0.150 0.151 0.152 0.153
[154] 0.154 0.155 0.156 0.157 0.158 0.159 0.160 0.161 0.162 0.163 0.164 0.165 0.166 0.167 0.168 0.169 0.170
[171] 0.171 0.172 0.173 0.174 0.175 0.176 0.177 0.178 0.179 0.180 0.181 0.182 0.183 0.184 0.185 0.186 0.187
[188] 0.188 0.189 0.190 0.191 0.192 0.193 0.194 0.195 0.196 0.197 0.198 0.199 0.200

> #同じ作業を中断
> sfSapply((1:200)/1000, function(x){Sys.sleep(x); x})

> #変数を変えて20個のみ実行したが、前の結果が残っている?
> sfSapply((1:20)/100, function(x){Sys.sleep(x); x})
  [1] 0.010 0.020 0.030 0.026 0.027 0.028 0.029 0.030 0.031 0.032 0.033 0.034 0.035 0.036 0.037 0.038 0.039
 [18] 0.040 0.041 0.042 0.043 0.044 0.045 0.046 0.047 0.048 0.049 0.050 0.051 0.052 0.053 0.054 0.055 0.056
 [35] 0.057 0.058 0.059 0.060 0.061 0.062 0.063 0.064 0.065 0.066 0.067 0.068 0.069 0.070 0.071 0.072 0.073
 [52] 0.074 0.075 0.076 0.077 0.078 0.079 0.080 0.081 0.082 0.083 0.084 0.085 0.086 0.087 0.088 0.089 0.090
 [69] 0.091 0.092 0.093 0.094 0.095 0.096 0.097 0.098 0.099 0.100 0.101 0.102 0.103 0.104 0.105 0.106 0.107
 [86] 0.108 0.109 0.110 0.111 0.112 0.113 0.114 0.115 0.116 0.117 0.118 0.119 0.120 0.121 0.122 0.123 0.124
[103] 0.125 0.126 0.127 0.128 0.129 0.130 0.131 0.132 0.133 0.134 0.135 0.136 0.137 0.138 0.139 0.140 0.141
[120] 0.142 0.143 0.144 0.145 0.146 0.147 0.148 0.149 0.150 0.151 0.152 0.153 0.154 0.155 0.156 0.157 0.158
[137] 0.159 0.160 0.161 0.162 0.163 0.164 0.165 0.166 0.167 0.168 0.169 0.170 0.171 0.172 0.173 0.174 0.175
[154] 0.176 0.177 0.178 0.179 0.180 0.181 0.182 0.183 0.184 0.185 0.186 0.187 0.188 0.189 0.190 0.191 0.192
[171] 0.193 0.194 0.195 0.196 0.197 0.198 0.199 0.200

> #残っているので再度実行して、うまくいっているように思われるが
> sfSapply((1:20)/100, function(x){Sys.sleep(x); x})
 [1] 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.10 0.11 0.12 0.13 0.14 0.15 0.16 0.17 0.18 0.19 0.20

> #やっぱり返る結果は意図としない結果。
> sfSapply((1:200)/1000, function(x){Sys.sleep(x); x})
 [1] 0.001 0.002 0.003 0.004 0.005 0.006 0.007 0.008 0.009 0.010 0.011 0.012 0.013 0.014 0.015 0.016 0.017
[18] 0.018 0.019 0.020 0.021 0.022 0.023 0.024 0.025 0.040 0.050 0.060 0.070 0.080 0.090 0.100 0.110 0.120
[35] 0.130 0.140 0.150 0.160 0.170 0.180 0.190 0.200

コア数の調査

以下のコードで取得できます。
なお、ハイパースレッディングを備えたCPUではスレッド数が取得されます。

> #parallelのdetectCoresを使用して取得します。
> library(parallel)
> detectCores()
[1] 4

並列計算事始め

参考文献・文書など

コメント



トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS