The documentation for the *apply() functions states:
vapplyis similar tosapply, but has a pre-specified type of return value, so it can be safer (and sometimes faster) to use. [Emphasis mine.]
It makes sense to me why it would be faster- less time wasted checking types - but, given that they could have said something like 'vapply() is as fast or faster than sapply()', but chose not to, I interpreted their choice of sometimes faster as them potentially meaning 'for most tasks vapply() is on average faster, but in some cases it could be on average the same speed, or in others even slower'- which seems quite odd to me. Why would it ever be slower? Advanced R states 'vapply() is faster than sapply()', which is fairly categorical, in contrast.
Am I misunderstanding this, or are there circumstances in which vapply() is slower than sapply(), and if so, what are they?
For example the rationale could be because of a difference in garbage collection, or the speed with which certain types are processed, or allocating memory or something else (these are wild guesses).
Research I've done:
Surprisingly, I couldn't find addressing this online, on StackOverflow or elsewhere. There's plenty of questions that reference vapply, and its safety. In a few comparisons, while vapply() was as fast or faster than sapply(), there were many iterations which are faster than the slowest vapply() iteration (and one where apply() was significantly faster than either lapply() or vapply(). So long story short, I'm a bit lost!
Any help you could provide would be greatly appreciated!
The checks which make
vapply()fast are not freeIn order to contrive circumstances where
vapply()is slower thansapply(), let's look at the source. Most of the work insapply()is done bylapply(). The C code forlapply()is quite simple. The relevant part is (comments mine):Essentially this creates an output list of the same length as the input list, iterating through it to set every element to the result of the user-provided function applied to each element of the input.
sapply()then runs the result throughsimplify2array().Conversely, the C code for
vapply()does a lot more work. A lot of this is optimisation which makes it quicker thansapply(), e.g. allocating an atomic vector immediately as the output, rather than allocating a list and then simplifying into a vector. However, it also contains this:We tell
vapply()the length and type of the output. This means that if, for example, if we tellvapply()that the output isinteger(1), it needs to check that each iteration produces an integer vector of length 1.A case where those checks are expensive
One way to create costly checks is to return a value where checking the length is expensive. Consider the simple example:
lapply()will run very quickly here.seq(1e9)produces anALTREP, an alternate representation. This means that rather than having to allocate a vector of length1e9, it allocates a much smaller object which essentially holds the start value, end value and increment. However, the docs forALTREPstate:This means
vapply()does not know that that this is anALTREP, and so it needs to check the length in a very costly way (much more costly than just runninglength()in R, which knows what anALTREPis).sapply()also has to do something costly. It basically does this:This creates a one-column
matrixwith1e9rows, i.e. it evaluates theALTREPinto a standard integer vector, so it allocates a large vector in RAM.So
vapply()andsapply()both have to do something considerably more expensive thanlapply(). The question is: which is costlier?Benchmarking the contrived case
Let's put this to the test:
Results
We can see here that
vapply()is substantially slower thansapply(). There are some caveats: these tests are just on my PC, and it was so slow that I only did three iterations. Also, I did have to do some playing around to get to here. With a vector of less than length1e9,vapply()is faster thansapply().Plot of results
Note that time is on a log scale.
It is worth pointing out that, fun as it was to engineer this situation, this is not typical. In the vast majority of tasks for which R is used,
vapply()is likely to be considerably faster thansapply(). Also, as you know, there are other benefits, such as thatvapply()ensures the return type is guaranteed.