ある処理にとても時間が掛かるため、一定時間経過後はその処理を途中で打ち切りたいときがある。
例えば、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)
 [1]  1  2  3  4  5  6  7  8  9 10

> print(end - start)
   ユーザ   システム       経過  
    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)
NULL

> print(end - start)
   ユーザ   システム       経過  
    0.164      0.316      5.185 

注意

与えられた関数は、別スレッドで動作するため、変数のスコープには注意が必要である。
具体的には、この関数内から関数外の変数には取得も設定もできない。
つまり、この関数の実行に必要となる変数はすべて引数で与え、結果は戻り値として受け取らなければならない。

まとめ

関数に時間制限を設ける方法をご紹介させていただいた。
Rの並列化は主に、for文やapply関数関連が多い。
しかし、parallelパッケージのmcparallel関数とmccollect関数を用いることができれば、さらに柔軟に並列化処理を組み込むことができるとわかっていただけたと思う。
今回ご紹介したソースコードが皆様のお役に立てれば幸いだ。

関連する記事

  • R言語 CRAN Task View:欠損データR言語 CRAN Task View:欠損データ CRAN Task View: Missing Dataの英語での説明文をGoogle翻訳を使用させていただき機械的に翻訳したものを掲載しました。 Maintainer: Julie Josse, Nicholas Tierney and Nathalie Vialaneix (r-miss-tastic […]
  • 平均的に分類する方法の考察(1)平均的に分類する方法の考察(1) ある定量データが与えられたとき、それを平均的に分類するための方法を考えてみる。 ここで言う平均的に分類するというのは、分類された各グループのデータ数がほぼ等しく、かつ、分類された各グループの平均値が定量データ全体の平均値にほぼ等しくなるように分類するということである。 具体的には、学生のクラス替えを行う際に、100人の学生を3つのクラスに振り分けたいが、その場合、各ク […]
  • なぜマイナス×マイナス=プラスとなるのかなぜマイナス×マイナス=プラスとなるのか 先日、ある人と話しているときに、私が大学で数学を学んだことを告げると、「なぜ、マイナス×マイナス=プラスとなるのか」と質問を受けた。 どうやら、「マイナス×マイナス=プラス」はルールとして覚えているようだった。 この質問を受けたとき、正直に言うと「すばらしい」と思った。なぜそう思ったのかというと、すでに当たり前として組み込まれている「マイナス×マイナス=プラス」に対し […]
  • Python CaboChaを用いて係り受け構造を抽出する方法Python CaboChaを用いて係り受け構造を抽出する方法 Pythonと日本語係り受け解析器であるCaboChaを用いて係る語と受ける語のペアを抽出する方法をご紹介する。 環境:Ubuntu14.04 Pythonツールのインストール PythonからCaboChaを扱うために、CaboChaに付属しているPythonのsetup.pyをインストールする。 これはPython2系専用であることに注意する。 caboch […]
  • R,knitPDF bxjsarticleを用いた際にサブタイトルでエラーが出る場合の対処法 RStudioを用いてRmdファイルから日本語PDFを作成する際に、ドキュメントクラスをbxjsarticleと設定すると、次のようなエラーが出てPDFファイルが作成されない。 エラーは、subtitleコマンドが二重に定義されているために起こっているようだ。 ! LaTeX Error: Command \subtitle already defined. Or […]
R 関数に時間制限を設ける方法