Reputation: 355
I'm running an algorithm several times through a for loop in R. My loop is very basic and looks like this.
iter <- 5 #number of iterations
result <- list()
for (i in 1:iter) {
fit <- algorithm() #this is an example function that starts the algorithm
result[[i]] <- print(fit)
}
The problem is that the running times vary greatly with each run. There are runs that take only 10 minutes, others take over an hour. However, I know that the longer running times are due to the fact that the algorithm has problems because of the initial values and that the results of these runs will be wrong anyway.
So, I am now looking for a solution that (1) interrupts the function (i.e. algorithm() in the example above) after e.g. 1000 seconds, (2) proceeds with the for loop and (3) adds an additional iteration for each interruption. So, in the end, I want results from five runs with a running time less than 1000 seconds.
Does anyone have an idea? Is this even technically possible? Thanks in advance!
Upvotes: 1
Views: 956
Reputation: 160447
I think you can use setTimeLimit
for this.
Quick demo:
setTimeLimit(elapsed = 2)
Sys.sleep(999)
# Error in Sys.sleep(999) : reached elapsed time limit
setTimeLimit(elapsed = Inf)
(It's important to note that you should return the time limit setting when you no longer desire its interruption.)
My "complex algorithm" will sleep a random length. Those random lengths are
set.seed(42)
sleeps <- sample(10, size=5)
sleeps
# [1] 1 5 10 8 2
I'm going to set an arbitrary limit of 6 seconds, beyond which the sleep will be interrupted and we'll get no return value. This should interrupt the third and fourth elements.
iter <- 5
result <- list()
for (i in seq_len(iter)) {
result[[i]] <- tryCatch({
setTimeLimit(elapsed = 6)
Sys.sleep(sleeps[[i]])
setTimeLimit(elapsed = Inf)
c(iter = i, slp = sleeps[[i]])
}, error = function(e) NULL)
}
result
# [[1]]
# iter slp
# 1 1
# [[2]]
# iter slp
# 2 5
# [[3]]
# NULL
# [[4]]
# NULL
# [[5]]
# iter slp
# 5 2
If you have different "sleeps" and you end up with a shorter object than you need, just append it:
result <- c(result, vector("list", 5 - length(result)))
I'll enhance this slightly, for a couple of things:
lapply
to for
loops when filling result
in this way; andon.exit
, which ensures that a function will be called when its enclosure exits, whether due to error or not.result <- lapply(seq_len(iter), function(i) {
setTimeLimit(elapsed = 6)
on.exit(setTimeLimit(elapsed = Inf), add = TRUE)
tryCatch({
Sys.sleep(sleeps[i])
c(iter = i, slp = sleeps[i])
}, error = function(e) NULL)
})
result
# [[1]]
# iter slp
# 1 1
# [[2]]
# iter slp
# 2 5
# [[3]]
# NULL
# [[4]]
# NULL
# [[5]]
# iter slp
# 5 2
In this case, result
is length 5, since lapply
will always return something for each iteration. (The use of lapply
is idiomatic for R, where its efficiencies are often in apply
and map
-like methods, unlike other languages where real speed is realized with literal for
loops.)
(BTW: instead of the on.exit
logic, I could have used tryCatch(..., finally=setTimeLimit(elapsed=Inf))
as well.)
An alternative to the on.exit
logic is to use setTimeLimit(.., transient=TRUE)
from within the execution block to be limited. That would make this code
result <- lapply(seq_len(iter), function(i) {
tryCatch({
setTimeLimit(elapsed = 6, transient = TRUE)
Sys.sleep(sleeps[i])
c(iter = i, slp = sleeps[i])
},
error = function(e) NULL)
})
One benefit of this is that regardless of the success/interruption of the limited code block, once that is done then the limit is immediately lifted, so there is less risk of inadvertently leaving it in place.
Upvotes: 2