Some setup stuff:

library(microbenchmark)

# Simpler microbenchmark output
print.microbenchmark <- function(x, ...) {
  mb_summary <- function(x) {
    res <- summary(x, unit="us")
    data.frame(name = res$expr, median = res$median)
  }
  
  print(mb_summary(x))
}

There’s a significant amount of overhead just from calling the R function get(). This is true even when you skip the pos argument and provide envir. For example, if you call get(), it takes much more time than .Internal(get()), which is what get() does.

If you already know that the object exists in an environment, it’s faster to use e$x, and slightly faster still to use e[["x"]]:

e <- new.env()
e$a <- 1

# Accessing objects in environments
microbenchmark(
  get("a", e, inherits = FALSE),
  get("a", envir = e, inherits = FALSE),
  .Internal(get("a", e, "any", FALSE)),
  e$a,
  e[["a"]],
  .Primitive("[[")(e, "a"),
  
  unit = "us"
)
#>                                    name median
#> 1         get("a", e, inherits = FALSE) 0.9960
#> 2 get("a", envir = e, inherits = FALSE) 0.9125
#> 3  .Internal(get("a", e, "any", FALSE)) 0.3010
#> 4                                   e$a 0.2275
#> 5                              e[["a"]] 0.1810
#> 6              .Primitive("[[")(e, "a") 0.2860

A similar thing happens with exists(): the R function wrapper adds significant overhead on top of .Internal(exists()). It’s also faster to use $ and [[, then test for NULL, but of course this won’t distinguish between objects that don’t exist, and those that do exist but have a NULL value:

# Test for existence of `a` (which exists), and `c` (which doesn't)
microbenchmark(
  exists('a', e, inherits = FALSE),
  exists('a', envir = e, inherits = FALSE),
  .Internal(exists('a', e, 'any', FALSE)),
  'a' %in% ls(e, all.names = TRUE),
  is.null(e[['a']]),
  is.null(e$a),

  exists('c', e, inherits = FALSE),
  exists('c', envir = e, inherits = FALSE),
  .Internal(exists('c', e, 'any', FALSE)),
  'c' %in% ls(e, all.names = TRUE),
  is.null(e[['c']]),
  is.null(e$c),

  unit = "us"
)
#>                                        name median
#> 1          exists("a", e, inherits = FALSE) 1.2210
#> 2  exists("a", envir = e, inherits = FALSE) 1.0750
#> 3   .Internal(exists("a", e, "any", FALSE)) 0.3655
#> 4          "a" %in% ls(e, all.names = TRUE) 7.7060
#> 5                         is.null(e[["a"]]) 0.3060
#> 6                              is.null(e$a) 0.3430
#> 7          exists("c", e, inherits = FALSE) 1.2655
#> 8  exists("c", envir = e, inherits = FALSE) 1.0270
#> 9   .Internal(exists("c", e, "any", FALSE)) 0.3590
#> 10         "c" %in% ls(e, all.names = TRUE) 7.4970
#> 11                        is.null(e[["c"]]) 0.2725
#> 12                             is.null(e$c) 0.3160

Appendix

sessionInfo()
#> R version 3.1.2 (2014-10-31)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
#>  [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
#> [10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] microbenchmark_1.3-0
#> 
#> loaded via a namespace (and not attached):
#> [1] digest_0.6.4     evaluate_0.5.5   formatR_1.0      htmltools_0.2.6  knitr_1.6       
#> [6] rmarkdown_0.3.13 stringr_0.6.2    tools_3.1.2      yaml_2.1.13