foreachでprogressbarを表示する
最近、pforeachという簡単に並列処理を行うパッケージが僕の中で話題です。とても便利です。
R で超簡単に並列処理を書けるパッケージ pforeach を作った - ほくそ笑む
ただpforeachを使って並列化できるのは良いのですが、並列処理だと普通にやってもprogressbarが表示できません。(僕が知らないだけかもしれないですが)
普通にやるとこうなります。
pb <- txtProgressBar(min = 0, max = 10, style = 3) pforeach(i = 1:10) ({ setTxtProgressBar(pb, i) sqrt(i) }) ## [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 ## [9] 3.000000 3.162278
ダメですね...
ということで考えました。案としては2つです。ループの前に出すか後に出すか。
案1 ループの前に出す
iterators
パッケージの関数をいじります。iterators::icount
関数に関しては、各ループの実行時にイテレータに渡す値を生成して返しています。だったら一緒にprogressbarの表示も一緒にやらせよう、といった発想です。
以下に、iterators::icount
関数に少し手を加えたpb_icount
関数を作成したのでこれを示します。
pb_icount <- function (count) { if (missing(count)) count <- NULL else if (!is.numeric(count) || length(count) != 1) stop("count must be a numeric value") i <- 0L pb <- txtProgressBar(min = 0, max = count, style = 3) # 追加 nextEl <- function() { if (is.null(count) || i < count) { setTxtProgressBar(pb, i+1) # 追加 (i <<- i + 1L) } else { cat("\n") # 追加 stop("StopIteration", call. = FALSE) } } it <- list(nextElem = nextEl) class(it) <- c("abstractiter", "iter") it }
これを使ってもう一度並列処理をやってみます。
N <- 10000 pforeach(i = pb_icount(N)) ({ sqrt(i) })[1:10] |========================================================================| 100% ## [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 ## [9] 3.000000 3.162278
やりました!!
ただ100%になってからちょっと間があるのが気になる所です。もしかしてループの割当を先に行っていたりするのでしょうか。そうであればこの案はボツなんですが。
他にはiterators::iter
関数はなかなか複雑で難しそうではありますが、こちらでもできればやりたいですね。
その他の関数については、iterators::icount
関数と似た構造であれば同様にできるのではないでしょうか。
案2 ループの後に出す
イテレータではなく、foreach
関数の引数.combine
に注目してprogress barを表示させている例がありました。
How do you create a progress bar when using the "foreach()" function in R? - Stack Overflow
これをもとにして、以下にpb
関数を作成しました。(08/16修正)
pb <- function(N, FUN = c) { pbar <- txtProgressBar(min = 2, max = N, style = 3) count <- 0L if(is.character(FUN)) { FUN <- get(FUN) } function(...) { count <<- count + length(list(...)) - 1L setTxtProgressBar(pbar, count) if(count == (N-1)) { cat("\n") } FUN(...) } }
では検証します。.combine
がc
関数でなくなるので、引数.multicombine
も付けておきます。
N <- 10000 pforeach(i = 1:N, .combine = pb(N), .multicombine = TRUE) ({ sqrt(i) })[1:10] |=======================================================================| 100% ## [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 ## [9] 3.000000 3.162278
やりました!!
でも実は.multicombine=TRUE
にしたことでループの回数が変化しているので、ループ回数を表すprogressbarにはならずcombine回数となっています。しかし進捗状況を表すという目的であれば十分だと思います。
速度比較
最後に速度比較です。
- progressbarなし
- 案1
- 案2
を比較します。
# progressbarなし system.time({ N <- 10000 pforeach(i = icount(N)) ({ sum(rnorm(10000)) })[1:10] }) ## user system elapsed ## 27.616 1.214 11.974 # 案1 system.time({ N <- 10000 pforeach(i = pb_icount(N)) ({ sum(rnorm(10000)) })[1:10] }) |========================================================================| 100% ## user system elapsed ## 16.797 0.945 12.455 # 案2 system.time({ N <- 10000 pforeach(i = icount(N), .combine = pb(N)) ({ sum(rnorm(10000)) })[1:10] }) |========================================================================| 100% ## user system elapsed ## 24.370 1.107 12.799
まとめ
案1(ループの前に出す)
- あらかじめ関数を作成する必要がある
- まだ
icount
関数しか対応できていない - 100%になってから完了まで少し間がある
案2(ループの後に出す)
- 引数にループ回数が必要
- 任意の関数に適用できる
- progressbarの進行速度が適切そう(これについては実行して確認してみてください)
.multicombine=TRUE
にすると(.maxcombine
を変化させると)progressbarの回数に影響する
.multicombine
でcombine回数が変化することで、案2ではループ回数を表すprogressbarにはなりませんが、進捗状況を表すという意味では案2は十分に良い策だと思います。
きちんとループ回数にしたいという場合は案1を使っていくしかないかもしれないです。