[Rd] Why is as.function() slower than eval(call("function"())?
Joshua Ulrich
josh.m.ulrich at gmail.com
Fri Aug 4 13:18:19 CEST 2017
On Thu, Aug 3, 2017 at 11:32 PM, Gregory Werbin
<outthere at me.gregwerbin.com> wrote:
> (Apologies if this is better suited for R-help.)
>
> On my system (macOS Sierra, late 2014 MacBook Pro; R 3.4.1, Homebrew build), I found that it is faster to construct a function using eval(call("function", ...)) than using as.function(list(...)). Example:
>
> make_fn_1 <- function(a, b) eval(call("function", a, b), env = parent.frame())
> make_fn_2 <- function(a, b) as.function(c(a, list(b)), env = parent.frame())
>
> a <- as.pairlist(alist(x = , y = ))
> b <- quote(x + y)
>
> library("microbenchmark")
> microbenchmark(make_fn_1(a, b), make_fn_2(a, b))
>
> # Unit: microseconds
> # expr min lq mean median uq max neval cld
> # make_fn_1(a, b) 1.671 1.8855 2.13297 2.039 2.1950 9.852 100 a
> # make_fn_2(a, b) 3.541 3.7230 4.13400 3.906 4.1055 23.153 100 b
>
> At first I thought the gap was due to the overhead of calling c(a, list(b)). But this turns out not to be the case:
>
> make_fn_weird <- function(a, b) as.function(c(a, b), env = parent.frame())
> b_wrapped <- list(b)
>
> make_fn_weirder <- function(a_b) as.function(a_b, env = parent.frame())
> a_b <- c(a, b_wrapped)
>
> microbenchmark(make_fn_1(a, b), make_fn_2(a, b),
> make_fn_weird(a, b_wrapped), make_fn_weirder(a_b))
>
> # Unit: microseconds
> # expr min lq mean median uq max neval cld
> # make_fn_1(a, b) 1.718 1.8990 2.12119 1.9860 2.1605 8.057 100 a
> # make_fn_2(a, b) 3.393 3.5865 4.03029 3.6655 3.9615 27.499 100 c
> # make_fn_weird(a, b_wrapped) 3.354 3.5005 3.77190 3.6405 3.9425 6.839 100 c
> # make_fn_weirder(a_b) 2.488 2.6290 2.83352 2.7215 2.8800 7.007 100 b
>
> One IRC user pointed out that as.function() takes its own path through the code, namely do_asfunction() (in src/main/coerce.c). What is it about this code path that's 50% slower than whatever happens during eval(call("function", a, b))?
>
> Obviously this is a trivial micro-optimization and it doesn't matter to 99% of users. Mostly asking out of curiosity, but also wondering if there's a more general lesson to be learned here.
>
Agreed that this is minor (~2us), but the majority of the difference
seems to be from S3 method dispatch. as.function() is generic and has
to dispatch to as.function.default(). The times are very similar if
you call the method directly.
R> make_fn_3 <- function(a, b) as.function.default(c(a, list(b)), env
= parent.frame())
R> microbenchmark(make_fn_1(a, b), make_fn_2(a, b), make_fn_3(a, b))
Unit: microseconds
expr min lq mean median uq max neval
make_fn_1(a, b) 1.615 1.7595 12.78339 1.9115 2.145 1077.657 100
make_fn_2(a, b) 3.077 3.3390 19.89423 3.5215 3.862 1589.505 100
make_fn_3(a, b) 1.629 1.7975 15.40389 1.9505 2.227 1335.306 100
Now the difference is <100ns, which is much harder to investigate.
> Thanks!
> ______________________________________________
> R-devel at r-project.org mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
--
Joshua Ulrich | about.me/joshuaulrich
FOSS Trading | www.fosstrading.com
R/Finance 2017 | www.rinfinance.com
More information about the R-devel
mailing list