Multiplying elements of list with lapply is almost twice as fast with in-line function definition than with standard "*"

Viewed 61

If we want to multiply of elements of a list by a constant, we can do so with lapply. However, I have observed that defining the function to be applied in-line is almost twice faster than specifying "*" as the function to be applied:

library(microbenchmark)
microbenchmark::microbenchmark(x=lapply(X=list(a=c(1,2,3)), FUN=function(x) x*1000), y=lapply(X=list(a=c(1,2,3)), "*", 1000), times = 10000)

This gives me a median of around 1100 nanoseconds for the first expression, and around 1900 nanoseconds for the second one.

Any ideas why this could be happening?

1 Answers

lapply calls match.fun, which must spend some time (well, about a microsecond) matching the string "*" to the primitive function `*`. Passing the function directly avoids the overhead.

l <- list(1, 2, 3)
microbenchmark::microbenchmark(lapply(l, function(x) x * 1000),
                               lapply(l, "*", 1000),
                               lapply(l, `*`, 1000),
                               times = 1e+06L)
## Unit: nanoseconds
##                             expr  min   lq     mean median   uq      max neval
##  lapply(l, function(x) x * 1000) 1271 1435 1614.497   1476 1517  1243981 1e+06
##             lapply(l, "*", 1000) 1640 1763 2026.791   1804 1886 16498605 1e+06
##             lapply(l, `*`, 1000)  861  984 1198.956   1025 1066 16636365 1e+06
microbenchmark::microbenchmark(match.fun(function(x) x * 1000),
                               match.fun("*"),
                               match.fun(`*`),
                               times = 1e+06L)
## Unit: nanoseconds
##                             expr min  lq      mean median  uq      max neval
##  match.fun(function(x) x * 1000)  82 164  249.0617    205 205 15783606 1e+06
##                   match.fun("*") 779 902 1036.1593    902 984 15515261 1e+06
##                   match.fun(`*`)  41 164  187.4243    164 164   588842 1e+06

That said, match.fun is never going to be a bottleneck, unless maybe you've written a function that calls match.fun a few billion times, so optimizing at this level would just be "for fun".

Related