Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NOTE: stopifnot() comes with some overhead and is getting slower in R (>= 3.5.0) #70

Open
HenrikBengtsson opened this issue Apr 12, 2018 · 3 comments

Comments

@HenrikBengtsson
Copy link
Owner

HenrikBengtsson commented Apr 12, 2018

Pasting in my old private notes to myself here:

Date: Sun, Sep 11, 2016, 22:16
Subject: R SPEEDUP: stopifnot()

stopifnot() is always calling match.call() even when there are no error.  It adds unnecessary overhead.

stopifnot <- function (...)
{
    n <- length(ll <- list(...))
    if (n == 0L)
        return(invisible())
    mc <- match.call()
    for (i in 1L:n) if (!(is.logical(r <- ll[[i]]) && !anyNA(r) && all(r))) {
        ch <- deparse(mc[[i + 1]], width.cutoff = 60L)
        if (length(ch) > 1L)
            ch <- paste(ch[1L], "....")
        stop(sprintf(ngettext(length(r), "%s is not TRUE", "%s are not all TRUE"),
            ch), call. = FALSE, domain = NA)
    }
    invisible()
}


stopifnot2 <- function (...)
{
    n <- length(ll <- list(...))
    if (n == 0L)
        return(invisible())
    for (i in 1L:n) if (!(is.logical(r <- ll[[i]]) && !anyNA(r) &&
        all(r))) {
        mc <- match.call()
        ch <- deparse(mc[[i + 1]], width.cutoff = 60L)
        if (length(ch) > 1L)
            ch <- paste(ch[1L], "....")
        stop(sprintf(ngettext(length(r), "%s is not TRUE", "%s are not all TRUE"),
            ch), call. = FALSE, domain = NA)
    }
    invisible()
}


stopifnot3 <- function (...)
{
    n <- length(ll <- list(...))
    if (n == 0L)
        return(invisible())
    for (i in 1L:n) if (!(is.logical(r <- .subset2(ll, i)) && !anyNA(r) && all(r))) {
        mc <- match.call()
        ch <- deparse(mc[[i + 1]], width.cutoff = 60L)
        if (length(ch) > 1L)
            ch <- paste(ch[1L], "....")
        stop(sprintf(ngettext(length(r), "%s is not TRUE", "%s are not all TRUE"),
            ch), call. = FALSE, domain = NA)
    }
    invisible()
}
library("microbenchmark")
stats <- microbenchmark(
  stopifnot(TRUE),
  stopifnot2(TRUE),
  stopifnot3(TRUE)
)
print(stats)
# Unit: microseconds
#              expr   min     lq      mean median    uq       max neval cld
#   stopifnot(TRUE) 4.150 4.3555  95.19823 4.4855 4.682  9040.550   100   a
#  stopifnot2(TRUE) 1.246 1.3605 115.90333 1.4555 1.549 11443.605   100   a
#  stopifnot3(TRUE) 1.248 1.3560  99.56975 1.4170 1.515  9811.166   100   a
library("microbenchmark")
x <- 1:1e6
stats <- microbenchmark(
  stopifnot(is.numeric(x), length(x) == 1e6),
  stopifnot2(is.numeric(x), length(x) == 1e6), ## clearly faster than stopifnot()
  stopifnot3(is.numeric(x), length(x) == 1e6) ## same speed as stopifnot2()
)
print(stats)
# Unit: microseconds
#                                           expr   min     lq    mean median     uq    max neval cld
#   stopifnot(is.numeric(x), length(x) == 1e+06) 4.613 4.8780 5.62728 5.0145 5.3120 43.986   100   b
#  stopifnot2(is.numeric(x), length(x) == 1e+06) 1.841 2.0095 2.18218 2.0720 2.2250  5.159   100  a 
#  stopifnot3(is.numeric(x), length(x) == 1e+06) 1.831 1.9770 2.09460 2.0455 2.1575  4.376   100  a 
@HenrikBengtsson
Copy link
Owner Author

HenrikBengtsson commented Apr 12, 2018

... and follow up note from 2018-03-15: Ouch... in R-devel, stopifnot() has become yet 4-5 times slower;

> source("stopifnot.R")
Unit: microseconds
                                          expr    min      lq     mean  median      uq    max
  stopifnot(is.numeric(x), length(x) == 1e+06) 15.308 15.7010 17.50978 15.9335 16.3295 71.253
 stopifnot2(is.numeric(x), length(x) == 1e+06)  1.489  1.6425  1.90277  1.7815  1.9605  8.348
 neval
   100
   100

which is due to a complete rewrite using tryCatch() and withCallingHandlers().

@HenrikBengtsson
Copy link
Owner Author

I've moved to using a private, simpler barebone stop_if_not() implementation, e.g. https://github.com/HenrikBengtsson/listenv/blob/251138d4576cfd0a531570698c338778af5fc76b/R/utils.R#L47-L61

@mmaechler
Copy link

Well, that version uses list(...) as pre R-3.5.0 did too ... not delivering what its documentation promised:
Expressions are evaluated "one by one" and the evaluation stops with an error as soon as one of the results is not all true. Suharto Anggono's proposals on the R-devel list will lead to a fast and good stopifnot() from R-3.6.0 on .. i.e., in less than 2 months from now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants