furrr パッケージを使うと
purrr パッケージのノリでモダンに並列処理ができるぞ!
purrr パッケージを使ったことがない人は下記のリンクを参考して欲しい.
- そろそろ手を出すpurrr by uribo 氏
- purrr — ループ処理やapply系関数の決定版 by Heavy Watal 氏
- Apply functions with purrr::CHEAT SHEET by RStudio 社
インストール
# CRAN から
install.packages("furrr")
# GitHub から
source("https://install-github.me/DavisVaughan/furrr")読み込み
library(furrr)## Loading required package: futurefurrr パッケージを利用するのに必要な
future パッケージも同時に読み込まれます.
使い方
基本は purrr パッケージと同じ.関数名は purrr パッケージの関数名 (map() など) の前に future_ をつける (i.e. future_map).
事前に plan() 関数を実行すると strategy 引数 に応じて並列処理をするかなどを選んでおける点が大きく異なる.
基本的にはシングルスレッドの plan(sequential) かマルチスレッドの plan(multiprocess) でいいと思う.複数のマシンで並列化するための cluster や remote もある.
シングルスレッド (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.8204684library(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.411plan(multiprocess)
system.time(future_map(1:4, ~ Sys.sleep(.1)))## user system elapsed
## 0.039 0.028 0.144現在の plan は plan() により確認できる.
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.134system.time(future_map(1:4, ~ Sys.sleep(.1)))## user system elapsed
## 0.029 0.021 0.135plan(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.206plan(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.9207303future_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.66327811set.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
Atusy's blog