例えば、for文で、あるループだけが重いため全体として時間が掛かってしまう場合、その処理を一旦スキップしてfor文の先に処理を進めたい、などです。
ここでは、そのひとつの解決策として、関数に時間制限を設けて、一定時間経過後はその関数を強制終了するコードをご紹介します。
ただし、このコードはLinux環境でのみ動作する点にご注意ください。
環境
Ubuntuのバーション
cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
Rのバージョン
R --version
R version 3.4.0 (2017-04-21) -- "You Stupid Darkness"
Copyright (C) 2017 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)
ソースコード
さっそくだが、関数に時間制限を設けるための関数のソースコードを以下に記載します。
簡単に説明すると、2つのスレッドを用意し、一方は与えられた関数を実行し、もう一方はその与えられた関数を監視します。
監視するスレッドは、一定時間が過ぎるまではLinuxのpsコマンドからプロセスID(PID)を抽出し、与えられた関数の処理が終了したら監視を終えます。
また、一定時間経過後は、与えられた関数の処理をLinuxのkillコマンドで強制終了して監視を終えます。
limitTime <- function(expr,
stop.seconds = 60 * 5,
interval.seconds = 1) {
require(parallel)
require(tools)
monitor <- function(x, stop.seconds, interval.seconds) {
s <- proc.time()
while (T) {
if ((proc.time() - s)[3] < stop.seconds) {
l.ps <- system("ps x", intern = T)
s.ps <- strsplit(l.ps, " ")
c.ps <- sapply(s.ps, function(x) {
return(x[2])
})
if (!(as.character(x$pid) %in% c.ps)) {
break
}
} else{
tools::pskill(pid = x$pid, signal = tools::SIGKILL)
break
}
Sys.sleep(interval.seconds)
}
}
p <- parallel::mcparallel(expr)
q <- parallel::mcparallel(monitor(p, stop.seconds, interval.seconds))
res <- parallel::mccollect(list(p, q))
return(res[[1]])
}
テスト
実際の動作を試してみます。
処理が完了する場合
重い処理が5秒、時間制限が10秒の場合を実行してみます。
heavyFunc <- function(x) {
Sys.sleep(5)
return(x)
}
start <- proc.time()
res <- limitTime(heavyFunc(1:10), stop.seconds = 10)
end <- proc.time()
実行結果は次のようになり、正常に処理が終了していることを確認することができます。
print(res)
(out) [1] 1 2 3 4 5 6 7 8 9 10
(out)
print(end - start)
(out) ユーザ システム 経過
(out) 0.192 0.624 5.329
処理を途中で打ち切る場合
重い処理が10秒、時間制限が5秒の場合を実行してみます。
heavyFunc <- function(x) {
Sys.sleep(10)
return(x)
}
start <- proc.time()
res <- limitTime(heavyFunc(1:10), stop.seconds = 5)
end <- proc.time()
実行結果は次のようになり、途中で打ち切られていることを確認することができます。
print(res)
(out)NULL
(out)
print(end - start)
(out) ユーザ システム 経過
(out) 0.164 0.316 5.185
注意
与えられた関数は、別スレッドで動作するため、変数のスコープには注意が必要です。
具体的には、この関数内から関数外の変数には取得も設定もできません。
つまり、この関数の実行に必要となる変数はすべて引数で与え、結果は戻り値として受け取らなければなりません。
まとめ
関数に時間制限を設ける方法をご紹介しました。
Rの並列化は主に、for文やapply関数関連が多いです。
しかし、parallelパッケージのmcparallel関数とmccollect関数を用いることができれば、さらに柔軟に並列化処理を組み込むことができます。
今回ご紹介したソースコードが皆様のお役に立てれば幸いです。