並列処理とは
Rでは、通常1つのプログラムを動かすために1つのCPU論理スレッドを使用しています。
現代のPCは、CPUの中に複数のCPUコアをもっており、さらに、そのコアを疑似的に分割して論理スレッドという単位で処理を行っています。
つまり、高性能のPCを持っていたとしても大抵の場合、数分の1の処理能力で1つのプログラムを実行することになります。
普通は何の問題もないのですが、大規模問題を解かせようとした場合、これでは困ります。
複数の論理スレッドを使って処理を行うことを並列処理といい便利な機能なので、
今回は、Rで並列処理を行う方法について、ご紹介したいと思います。

ちなみに、複数のPCを使って大規模問題を解く場合もあり、これは分散処理といいます。
論理スレッド確認方法
自分のPCの論理スレッド数がわからない方のために論理スレッド数の確認方法をご紹介します。
Windowsの場合ですが、
タスクマネージャー→パフォーマンス→CPU
と表示すると画像のように、CPUの使用率やコア数、論理スレッド数が表示されます。
私は奮発して高価なPCを購入したので、論理スレッド数が多めになっています。(汗)

並列処理で高速化する問題
並列処理で注意していただきたいのは、すべての問題が高速化するわけではない点です。
図解した通り、複数の論理スレッドを使用して処理を高速化しますが、処理の内容は同じものでなければなりません。
つまり、1つの処理が終わらないと次の処理に進めないタイプの問題だと並列処理を行っても高速化できないので、注意が必要です。
イメージしやすいようにパンを複数人で作る場合を例に説明したいと思います。
高速化する例
CPU1個がそれぞれ1つのパンを作っていくような問題の場合は、並列処理により高速化します。

画像の出典:いらすとや
高速化しない例
CPU1がパンをこね、CPU2がパンを焼き、CPU3がパンを運ぶパターンのように、工程ごとに役割を分けてしまうような問題だと、前の処理で詰まってしまうので、並列処理では高速化できません。
同じ問題でも解かせ方によって高速化しない場合があるので、注意してください。

画像の出典:いらすとや
ライブラリの使い方
ここまで説明してきた並列処理をRで行う方法をご紹介します。
今回紹介するパッケージは、snowです。
snowインストール方法
Rのコンソール画面で下記実行し、snowをインストールします。
install.packages("snow")
Rstudioユーザーの場合は
Packes → Install から「snow」と入力し、インストールすると早いです。

snow使い方
並列処理の問題として、1,000万回の足し算を100個分取得する問題を考えます。
通常の処理では、100回のfor文の中に1,000万回のfor文が入るプログラムとなり、
並列処理では、1,000万回のfor文をCPU100個分で処理することになります。
(論理スレッドは20個なので、1つ5回処理を行います。)
通常のfor文に比べて並列処理では、どのくらい早くなるか実際のプログラムで確認していきます。
下記がプログラム例です。
#ライブラリの読み込み
library(snow)
# for文、apply文、並列処理の結果格納用
for_result <- NULL
apply_result <- NULL
parApply_result <- NULL
#####----- for文の場合 -----#####
t <- proc.time() # 計測開始
# 100回のfor文
for (i in 1:100) {
# 足し算用
x <- 0
# 1,000万回のfor文
for (j in 1:10000000) {
x <- x + 1 # 足し算
}
# 足し算の解を格納
for_result <- c(for_result, x)
}
proc.time() - t # 計測結果
#for文の解
for_result
#####----- apply文の場合 -----#####
t <- proc.time() # 計測開始
# 足し算用
y <- matrix(0, 100, 1) # 100行1列
# 100回のapply文
apply_result <- apply(y, 1, function(x){
# 1,000万回のfor文
for (j in 1:10000000) {
x <- x + 1 # 足し算
}
# 戻り値
x
})
proc.time() - t # 計測結果
#apply文の解
apply_result
#####----- 並列処理の場合 -----#####
t <- proc.time() # 計測開始
# 足し算用
y <- matrix(0, 100, 1) # 100行1列
# 並列処理用のクラスター生成。論理スレッドをいくつ使うか指定できる。
# 今回は15個
cl <- makeCluster(15, type="SOCK")
clusterExport(cl, "y") # 変数を各論理スレッドで使用できるようにエクスポート
# 並列処理開始
parApply_result <- parApply(cl, y, 1, function(x){
# 1,000万回のfor文
for (j in 1:10000000) {
x <- x + 1 # 足し算
}
# 戻り値
x
})
stopCluster(cl) #並列処理終了
proc.time() - t # 計測結果
#parApply文の解
parApply_result
apply関数
Rにはfor文に似た機能としてapply()という関数が存在します。
これは、行列に対して繰り返し処理を適用して、結果を行列で返すという関数です。
for文を高速化してくれるので、よく使います。
また、並列処理をする際は、このapply関数が派生したparApply関数を使用します。
イメージは図の通りで、初めにセットした行列を初期値として繰り返し処理を実行し、その解を繰り返した数だけのベクトルまたは行列で返してくれます。

関数の解がベクトルだった場合、それを繰り返した分だけ記録が残るので、出力結果は行列となります。
使い方は、下記の通りです。
apply(初期値用の行列y, 1, function(){})
2番目の要素で1と指定すると行に対して繰り返しを行い、2を指定すると列に対して繰り返しを行います。
parApply関数の場合は、
parApply(並列処理用クラスターcl, 初期値用の行列y, 1, function(){})
となります。
出力結果の確認
for文の計算時間
> proc.time() - t # 計測結果
ユーザ システム 経過
14.37 0.09 14.50
apply文の計算時間
> proc.time() - t # 計測結果
ユーザ システム 経過
14.22 0.10 14.33
並列処理parApplyの計算時間(論理スレッド15個)
> proc.time() - t # 計測結果
ユーザ システム 経過
0.00 0.08 4.22
となり、並列処理により処理時間が1/3以下になりました。
本来は、15倍の処理能力を持っているので、もっと大規模な問題だとさらに処理時間が短くなると思います。
また、タスクマネージャーの出力を見ても並列処理をした場合、CPUの使用率が100%近くまで行っているのが確認できます。

念のため、各計算結果も確認すると、すべて同じで下の解でした。
> #parApply文の解
> parApply_result
[1] 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07
[15] 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07
[29] 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07
[43] 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07
[57] 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07
[71] 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07
[85] 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07 1e+07
[99] 1e+07 1e+07
Rのおすすめの本をご紹介
Rでのデータ分析を体系的に学べるおすすめの本です。
ちょっと古いですが、Rの有名な本です。
まとめ
いかがだったでしょうか。Rで並列処理を行う方法についてまとめてみました。
私自身学生時代にRで並列処理をしていた経験からこの記事を書いてみました。
同じような方の役に立てればうれしいです。
ミスがあれば修正しますので、ツイッターなどで教えていただけるとありがたいです。

コメント