furrr パッケージで R で簡単並列処理

カテゴリ: r

furrr パッケージを使うと purrr パッケージのノリでモダンに並列処理ができるぞ!

purrr パッケージを使ったことがない人は下記のリンクを参考して欲しい.

インストール

# CRAN から
install.packages("furrr")

# GitHub から
source("https://install-github.me/DavisVaughan/furrr")

読み込み

library(furrr)
## Loading required package: future

furrr パッケージを利用するのに必要な future パッケージも同時に読み込まれます.

使い方

基本は purrr パッケージと同じ.関数名は purrr パッケージの関数名 (map() など) の前に future_ をつける (i.e. future_map).

事前に plan() 関数を実行すると strategy 引数 に応じて並列処理をするかなどを選んでおける点が大きく異なる.

基本的にはシングルスレッドの plan(sequential) かマルチスレッドの plan(multiprocess) でいいと思う.複数のマシンで並列化するための clusterremote もある.

シングルスレッド (strategy = sequential)

set.seed(1)
future_map(1:3, rnorm)
## [[1]]
## [1] -0.6264538
## 
## [[2]]
## [1]  0.1836433 -0.8356286
## 
## [[3]]
## [1]  1.5952808  0.3295078 -0.8204684
library(purrr)
set.seed(1)
map(1:3, rnorm)
## [[1]]
## [1] -0.6264538
## 
## [[2]]
## [1]  0.1836433 -0.8356286
## 
## [[3]]
## [1]  1.5952808  0.3295078 -0.8204684

同じ結果になりましたね!

マルチスレッド (strategy = multiprocess)

plan(multiprocess) にした上で future_map などを実行するだけ.

試しに Sys.sleep(.1) してみると, plan(sequential) ではイテレータの数に比例した時間がかかるが, plan(multiprocess) では単純にはイテレータの数に比例しない.

plan(sequential)
system.time(future_map(1:4, ~ Sys.sleep(.1)))
##    user  system elapsed 
##   0.010   0.000   0.411
plan(multiprocess)
system.time(future_map(1:4, ~ Sys.sleep(.1)))
##    user  system elapsed 
##   0.039   0.028   0.144

現在の planplan() により確認できる.

plan()
## multiprocess:
## - args: function (expr, envir = parent.frame(), substitute = TRUE, lazy = FALSE, seed = NULL, globals = TRUE, workers = availableCores(), gc = FALSE, earlySignal = FALSE, label = NULL, ...)
## - tweaked: FALSE
## - call: plan(multiprocess)

一度設定した plan は新しいものを設定するまで継続する点に注意.

system.time(future_map(1:4, ~ Sys.sleep(.1)))
##    user  system elapsed 
##   0.031   0.017   0.134
system.time(future_map(1:4, ~ Sys.sleep(.1)))
##    user  system elapsed 
##   0.029   0.021   0.135
plan(sequential)
system.time(future_map(1:4, ~ Sys.sleep(.1)))
##    user  system elapsed 
##   0.005   0.000   0.407

コア数を変更

並列化する際に設定した plan(multiprocess)plan(multiprocess(workers = future::availableCores())) に相当し,自動的に最大数のコアを利用する.

従って, plan(multiprocess(workers = 2)) などとすることで,コア数を変更できる.

例えば multiprocess(workers = 1) の時は multiprocess(workers = 2) の時のおよそ2倍の時間が経過することが確認できる.

plan(multiprocess(workers = 1))
system.time(future_map(1:2, ~ Sys.sleep(.1)))
##    user  system elapsed 
##   0.006   0.000   0.206
plan(multiprocess(workers = 2))
system.time(future_map(1:2, ~ Sys.sleep(.1)))
##    user  system elapsed 
##   0.008   0.016   0.120

乱数を固定

future_map() 関数などには .options 引数があり,ここに future_options() 関数の実行結果を指定することで,挙動を弄れる.

例えば乱数を固定したい場合は, future_options() 関数の seed 引数を弄る.

future_map(1:3, rnorm, .options = future_options(seed = 1L))
## [[1]]
## [1] 1.377567
## 
## [[2]]
## [1] -1.7371292 -0.2660342
## 
## [[3]]
## [1] -0.1362109  1.8652161  0.9207303
future_map(1:3, rnorm, .options = future_options(seed = 1L))
## [[1]]
## [1] 1.377567
## 
## [[2]]
## [1] -1.7371292 -0.2660342
## 
## [[3]]
## [1] -0.1362109  1.8652161  0.9207303

seed 引数を整数値で指定する場合は, seed = 1L といった具合に整数であることを明示しなければならない. seed = 1 は実数値を指定した扱いになりエラーを返す. Isssue が建っているので,整数に見える実数も許可される未来もあるかもしれない.

set.seed では固定できない

set.seed(1)
future_map(1:3, rnorm)
## [[1]]
## [1] 1.666263
## 
## [[2]]
## [1] 0.3064038 1.6142811
## 
## [[3]]
## [1] -0.11573003  0.01955436 -0.66327811
set.seed(1)
future_map(1:3, rnorm)
## [[1]]
## [1] -0.8358542
## 
## [[2]]
## [1] 0.4818807 1.6501733
## 
## [[3]]
## [1] -1.0208719  0.2089296  0.4123071

プログレスバーを表示

future_map(..., .progress = TRUE) すればいいらしいが……?当方の環境ではなぜか表示されない.

Note that these are still a bit experimental so feedback is welcome.

とのこと.

plan(sequantial) の時は表示されないらしいが,改善要望が出ている.

また,経過時間を表示したいなど色々 feedback がされている模様.

出力の型

purrr の派生なので, _chr, _dfr などの suffix をつけて型をコントロールすることも勿論できる.

future_map_chr(1:3, ~ letters[.x])
## [1] "a" "b" "c"
future_map_dfr(1:3, ~ iris[.x * 50, ])
##   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
## 1          5.0         3.3          1.4         0.2     setosa
## 2          5.7         2.8          4.1         1.3 versicolor
## 3          5.9         3.0          5.1         1.8  virginica