//--------------------------------------------------------------------------------
//少ない知識ですが、恩返しと思いページを立ち上げています。
//加筆修正、大歓迎です。  markovchainmontecarlo(2015/09)
//--------------------------------------------------------------------------------

&color(red){&size(30){並列計算(snowfall)};};~
~
マルチコアが主流となってきているので、並列計算のパッケージである「snowfall」を紹介します。~
なお、特に断りが無い場合はSocketクラスターを使用しています。~
~
&color(red){&size(20){現在編集中};};~
----
&color(red){&size(20){目次};};~
#contents
----
*並列計算で計算速度は向上するか? [#ze3e7311]
並列化できる割合によって、理論上の限外が変わってきます。~
([[アムダールの法則:https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A0%E3%83%80%E3%83%BC%E3%83%AB%E3%81%AE%E6%B3%95%E5%89%87]]を参照)~
なお、&color(red){かえって遅くなる};場合もあります。~
並列化の概要は、このページ後述の[[並列計算事始め>#wdd7a7b8]]を参照してください。~
*対象者 [#jc285812]
計算速度を向上させたいが、煩わしいことはなるべく避けたい。~
そのような方にとって、並列計算パッケージ「snowfall」は一つの方法かもしれません。~

本記事は、以下の方を想定しています。~
-[[Rコード最適化のコツと実例集]]を読んで、ベクトル化等出来ることはは既に行っている。~
-apply family の使い方は分かっている。~
-モンテカルロシミュレーションなどの繰り返しを大量に行う必要がある。~

レベルとしては以下の方~
-Rの強みであるベクトル化は意識しなくても行っている。
-apply familyは内部でforを用いているため、「適切に」forを使用した場合は、apply familyよりも速度が速いことを知っている。
-[[Tierney 氏の R バイトコンパイラー]]も知っていて、必要な関数には適用できる。
-独立試行を大量に行わなくてはならないが、マルチコアでありながらCPU使用率が低いことにちょっと不満 <- &color(red){このレベル};

高速化の手段はいくつかありますので、適切な方法を用いてください。~
[[CRAN Task View: High-Performance and Parallel Computing with R:https://cran.r-project.org/web/views/HighPerformanceComputing.html]]
-C言語が得意な方 -> 「Rcpp」パッケージを利用すると爆速になります。(C言語を学ぶ必要があるため、「ちょっと試そうかな」という場合には向きません)
-CUDA対応のグラフィックボードを持っている。 -> gputools等を使用すると並列計算が速くなる可能性があります。
*snowfallの利点 [#d113b4a7]
-apply family など馴染みの関数で簡単に並列化が出来る。
-引数がほぼbaseの関数と一緒。&br;(snowではクラスターを引数に加える必要があるため、引数が若干異なる。)
-通常(Sequential Mode)処理から並列計算の処理へ移行する場合でも、プログラム本体の書き換えが不要。&br;(通常でテストをした後に並列化を実行できる。)
*とりあえず動かす [#jf6ffc48]
+ライブラリーの読み込み
+sfInitでクラスターを準備する。
+(必要があれば)ワーカーに変数やオブジェクトを渡す。
+snowfall 関数を使用して並列計算を行う。
+クラスターを止める
 > 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
*代表的な関数 対応表 [#r8506a76]
|CENTER:base|CENTER:snow|CENTER:snowfall|CENTER:コメント|h
|apply|parApply|sfApply||
|lapply|parLapply|sfLapply||
|sapply|parSapply|sfSapply||
|apply(X, MARGIN=1, FUN, ...)|parRapply|%%sfRapply%%&br;未実装(Version 1.84-4)|snowfallでは関数は定義されているが、以下のとおり。&br;sfRapply does not exists yet. Use Snow's parRapply instead.&br;&br;sfApplyで代用する。|
|apply(X, MARGIN=2, FUN, ...)|parCapply|%%sfCapply%%&br;未実装(Version 1.84-4)|snowfallでは関数は定義されているが、以下のとおり。&br;sfCapply does not exists yet. Use Snow's parCapply instead.&br;&br;sfApplyで代用する。|
|mapply|clusterMap|%%sfClusterMap%%&br;未実装(Version 1.84-4)|snowfallでは関数は定義されているが、以下のとおり。&br;Currently no wrapper for clusterMap&br;&br;snowfallの関数では代用できないため、並列化をする場合はsnowパッケージを使用する。|
|%*%|parMM|sfMM||
*snow snowfall 各種関数 対応表 [#hf8072c9]
カテゴリーは主観で分けています。
|CENTER:カテゴリー|CENTER:snow|CENTER:snowfall|CENTER:コメント|h
|クラスター作成|makeCluster|sfInit|すべてsfInitで行えます。|
|~|makeSOCKcluster|~|~|
|~|makePVMcluster|~|~|
|~|makeMPIcluster|~|~|
|~|makeNWScluster|~|~|
|オブジェクトなどの引渡し|clusterExport|sfExport|各クラスターに特定のオブジェクトをエクスポートします。|
|~|CENTER:-|sfExportAll|各クラスターに全てのオブジェクトをエクスポートします。|
|~|CENTER:-|sfRemove|各クラスターから特定のオブジェクトを削除します。|
|~|CENTER:-|sfRemoveAll|各クラスターから全てのオブジェクトを削除します。|
|~|CENTER:-|sfLibrary|各クラスターにライブラリーを読み込ませます。|
|~|CENTER:-|sfSource|各クラスターにソースファイルを読み込ませます。|
|並列基本関数|parApply|sfApply||
|~|parLapply|sfLapply||
|~|parSapply|sfSapply||
|~|parRapply|%%sfRapply%%|未実装(Version 1.84-4)|
|~|parCapply|%%sfCapply%%|未実装(Version 1.84-4)|
|~|parMM|sfMM|%*%|
|~|clusterMap|%%sfClusterMap%%|未実装(Version 1.84-4)|
|その他並列関数|clusterApply|sfClusterApply|sfClusterApplyはクラスター数以下の要素しか受け取れない。&br;(使えない関数の気がします。)|
|~|clusterApplyLB|sfClusterApplyLB|clusterApplyでは、1要素の計算が終わるまですべてのクラスターが待機していましたが&br;これは1要素の計算が終了次第次の計算に移ります。&br;(%%%L%%%oad %%%B%%%alanced version of sfClusterApply)|
|~|CENTER:-|sfClusterApplySR|1要素の計算が終了するごとにsaveを行い、予期せぬ終了(サーバーメンテナンスなど)があった場合でも、続きから計算を始めることが出来ます。&br;(ただし、saveを頻繁に行うので遅くなるようです。)&br;(%%%S%%%aves intermediate %%%R%%%esults)|
|~|clusterCall|sfClusterCall|各クラスターに同一の命令文を実行させます。|
|~|clusterEvalQ|sfClusterEval|各クラスターに同一の未評価の命令文を実行させます。|
|乱数|clusterSetupRNG |sfClusterSetupRNG|乱数の生成をします。&br;(set.seedのようにNULLによって乱数の生成が出来ないため、少々扱いにくいです。)|
|~|clusterSetupRNGstream |sfClusterSetupRNGstream|~|
|~|clusterSetupSPRNG |sfClusterSetupSPRNG|~|
|クラスターの終了|stopCluster|sfStop||
|クラスターの使用状況|snow.time|CENTER:-|クラスターの使用状況を調査出来る関数です。|
|~|print|CENTER:-|printを拡張し、snow.timeで取得したクラスターの使用状況表示します。|
|~|plot|CENTER:-|plotを拡張し、snow.timeで取得したクラスターの使用状況表示します。|
|デバッグ|CENTER:-|sfCat|各クラスターにcat文を実行させます。|
|各種情報等|clusterSplit|sfClusterSplit|helpを参照の事。|
|~|setDefaultClusterOptions|CENTER:-|~|
|~|getMPIcluster|CENTER:-|~|
|~|CENTER:-|sfParallel|~|
|~|CENTER:-|sfIsRunning|~|
|~|CENTER:-|sfCpus|~|
|~|CENTER:-|sfNodes|~|
|~|CENTER:-|sfGetCluster|~|
|~|CENTER:-|sfType|~|
|~|CENTER:-|sfSession|~|
|~|CENTER:-|sfSocketHosts|~|
|~|CENTER:-|sfSetMaxCPUs|~|
|~|CENTER:-|sfRestore|~|
*どの並列関数を使用するべきか [#w7f528f9]
**snow.time でクラスターの使用状況を探る [#k4c6f72c]
**clusterApplyとclusterApplyLB(snow) [#m097f183]
関数snow.timeを使用することによって、各クラスターの使用状況を確認できます。~
 > print(snow.time(何らかの並列処理))
 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で詳細に状況を確認できます。
-緑の四角:クラスター使用中~
-青の実線:結果をマスターに返すために待機中~
-赤の実線:マスターとクラスター間でデータの受け渡し~
-黒の破線:クラスター待機中~
&ref(fig00_snowtime.jpg);
**applyとclusterApply(snow) [#e0952afc]
applyでの実行時間が53.03秒に対して、並列計算を行ったclusterApplyは21.76秒!~
何と2倍以上の高速化が実現!素晴らしい!~
&ref(fig01_apply.jpg);&ref(fig01_clusterApply.jpg);~
-実行結果
 > #準備
 > library(snow)
 > cl <- makeSOCKcluster(4)
 > set.seed(123)
 > x <- sample(1:20)/4
 > 
 > #並列計算
 > (st.apply <- snow.time(apply(t(x), 2, Sys.sleep)))
 $elapsed
 [1] 53.03
 
 $send
 [1] 0
 
 $receive
 [1] 0
 
 > (st.clusterApply <- snow.time(clusterApply(cl, x, Sys.sleep)))
 elapsed    send receive  node 1  node 2  node 3  node 4 
   21.76    0.02    0.00   13.73   12.23   14.51   11.99 
 > 
 > #グラフ表示
 > dev.new(); plot(st.apply, title = "apply")
 > dev.new(); plot(st.clusterApplyLB, title = "clusterApplyLB")
 > 
 > #後処理
 > stopCluster(cl)
 > 
-コード
 #準備
 library(snow)
 cl <- makeSOCKcluster(4)
 set.seed(123)
 x <- sample(1:20)/4
 
 #並列計算
 (st.apply <- snow.time(apply(t(x), 2, Sys.sleep)))
 (st.clusterApply <- snow.time(clusterApply(cl, x, Sys.sleep)))
 
 #グラフ表示
 dev.new(); plot(st.apply, title = "apply")
 dev.new(); plot(st.clusterApplyLB, title = "clusterApplyLB")
 
 #後処理
 stopCluster(cl)
**clusterApply(snow)とclusterApplyLB(snow) [#m097f183]
clusterApplyは、各クラスターに1個引数を渡し、すべてのクラスターの計算終了を待って次の引数をクラスターに渡します。~
そのため、実行時間に差がある場合は待機状態になるクラスターが発生してしまいます。~
clusterApplyは確かに速くなっているが、クラスターの待機時間があるのでこれを減らすようにしたい。~
そこで出てきたのが、clusterApplyLBです。~
clusterApplyLBは、計算が終了したら次の引数を引き渡すので、待機状態になるクラスターがありません!~
&ref(fig01_clusterApply.jpg);&ref(fig02_clusterApplyLB.jpg);
-実行結果
 > #準備
 > library(snow)
 > cl <- makeSOCKcluster(4)
 > set.seed(123)
 > x <- runif(20)*2
 > x <- sample(1:20)/4
 > 
 > #並列計算
 > (st.clusterApply <- snow.time(clusterApply(cl, x, Sys.sleep)))
 elapsed    send receive  node 1  node 2  node 3  node 4 
    9.28    0.01    0.03    5.38    3.80    4.66    8.17 
   21.75    0.00    0.02   13.77   12.24   14.50   12.00 
 > (st.clusterApplyLB <- snow.time(clusterApplyLB(cl, x, Sys.sleep)))
 elapsed    send receive  node 1  node 2  node 3  node 4 
    6.43    0.00    0.02    5.12    6.41    4.74    5.79 
   13.76    0.00    0.00   13.01   13.74   12.76   13.02 
 > 
 > #グラフ表示
 > dev.new(); plot(st.clusterApply, title = "clusterApply")
 > dev.new(); plot(st.clusterApplyLB, title = "clusterApplyLB")
 > 
 > #後処理
 > stopCluster(cl)
 > 
-コード
 #準備
 library(snow)
 cl <- makeSOCKcluster(4)
 set.seed(123)
 x <- runif(20)*2
 x <- sample(1:20)/4
 
 #並列計算
 (st.clusterApply <- snow.time(clusterApply(cl, x, Sys.sleep)))
 (st.clusterApplyLB <- snow.time(clusterApplyLB(cl, x, Sys.sleep)))
 
 #グラフ表示
 dev.new(); plot(st.clusterApply, title = "clusterApply")
 dev.new(); plot(st.clusterApplyLB, title = "clusterApplyLB")
 
 #後処理
 stopCluster(cl)
**sfClusterApplyLBとsfLapply [#o8dfeaa8]
-実行結果
-コード
**sfClusterApplySR [#e56ac7c7]
-実行結果
-コード
**結局どの関数を使えばいいの? [#hb1a7b1c]
*Tips [#uf0ea58a]
**通常処理と並列処理の切り替え [#l2899007]
**R起動時のsnowfallの設定 [#icb8d0f1]
**clusterMap(snow) [#ed6fc913]
**乱数の使用 [#t017dce9]
**本当に速くなりますか? [#bbda15a6]
並列化できる割合によって、理論上の性能向上の限界が変わってきます。([[アムダールの法則:https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A0%E3%83%80%E3%83%BC%E3%83%AB%E3%81%AE%E6%B3%95%E5%89%87]]を参照)~
並列化できるタスクが90%ある場合性能向上の限界は10倍、半分しかない場合は2倍となります。~
ここで並列化できないタスクとは各workerとの通信などを指します。~
なお、&color(red){かえって遅くなる};場合もあります。~
**クラスターの種類 [#mb615e37]
|CENTER:クラスター|CENTER:コメント|h
|SOCK|デフォルト。&br;追加パッケージ不要。ワークステーションやPCおよびノートPCに適する。|
|MPI|パッケージRmpiが必要。|
|PVM|パッケージrpvmが必要。|
|NWS|追加パッケージ不要。|
**並列計算を中断した場合 [#zaa1454b]
並列計算を中断した場合は、必ず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
**コア数を知りたい。 [#w237cd57]
以下のコードで取得できます。~
なお、ハイパースレッディングを備えたCPUではスレッド数が取得されます。~
 >#parallelのdetectCoresを使用して取得します。
 > library(parallel)
 > detectCores()
 [1] 4
*並列計算事始め [#wdd7a7b8]

*参考文献・文書など [#mae70577]
-DOCUMENT
 help(package=snow)
 help(package=snowfall)
 vignette("snowfall")
-CRAN~
[[CRAN Task View: High-Performance and Parallel Computing with R:https://cran.r-project.org/web/views/HighPerformanceComputing.html]]~
-書籍~
[[Parallel R (英語):http://www.amazon.co.jp/Parallel-R-Q-Ethan-McCallum/dp/1449309925]]~
-rjpwiki~
[[Rで並列計算]]~
[[Tierney 氏の R バイトコンパイラー]]~
[[Rコード最適化のコツと実例集]]~
[[Rの関数定義の基本]]~
-Web Page~
[[snow Simplified:http://www.sfu.ca/~sblay/R/snow.html]]
[[Easier Parallel Computing in R with snowfall and sfCluster:http://journal.r-project.org/archive/2009-1/RJournal_2009-1_Knaus+et+al.pdf]]~
[[Tutorial Parallel computing using R package snowfall:http://www.informatik.uni-ulm.de/ni/staff/HKestler/Reisensburg2009/PDF/snowfall-tutorial.pdf]]~
[[並列プログラミング入門(OpenMP編):http://www.hpci-office.jp/invite2/documents2/openmp_2014-11-25.pdf]]~
[[snowパッケージで並列化(R):https://sites.google.com/site/scriptofbioinformatics/bing-lie-hua-guan-xi/snowpakkejide-bing-lie-hua-r]]~
[[【R言語】ループ処理回避 (and/or) 並列化処理 組み合わせで、計算高速化 手法まとめ:http://qiita.com/HirofumiYashima/items/18d5aa87578115215230]]~
[[Rのちょっと速いコードの書き方:http://www.anlyznews.com/2012/02/r_11.html]]~
[[R の apply 徹底解説:http://d.hatena.ne.jp/a_bicky/20120425/1335312593]]~
*コメント[#lbb783b6]
- とりあえずページを立ち上げました。 -- [[markovchainmontecarlo]] &new{2015-09-26 (土) 09:14:04};
- 修正加筆、大歓迎です。 -- [[markovchainmontecarlo]] &new{2015-09-26 (土) 09:41:45};

#comment


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Google
WWW を検索 OKADAJP.ORG を検索