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: future
furrr
パッケージを利用するのに必要な
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.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
現在の 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.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