And yet,powers2()still contains a loop. Can we do better? It would seem
that this setting is perfect forouter(), whose call form is
outer(X,Y,FUN)
This call applies the functionFUN()to all possible pairs of elements ofX
and elements ofY. The default value ofFUNis multiplication.
Here, we can write the following:
powers3 <- function(x,dg) return(outer(x,1:dg,"^"))
For each combination of element ofxand element of1:dg(resulting in
length(x)×dgcombinations in all),outer()calls the exponentiation function
^on that combination, placing the results in alength(x)×dgmatrix. This is
exactly what we need, and as a bonus, the code is quite compact. But is the
code faster?
system.time(powers3(x,8))
user system elapsed
1.336 0.204 1.747
What a disappointment! Here, we’re using a fancy R function, with very
compact code, but getting the worst performance of the three functions.
And it gets even worse. Here’s what happens if we try to make use of
cumprod():
powers4
function(x,dg) {
repx <- matrix(rep(x,dg),nrow=length(x))
return(t(apply(repx,1,cumprod)))
}
system.time(powers4(x,8))
user system elapsed
28.106 1.120 83.255
In this example, we made multiple copies ofx, since the powers of a
numbernare simplycumprod(c(1,n,n,n...)). But in spite of dutifully using
two C-level R functions, the performance was disastrous.
The moral of the story is that performance issues can be unpredictable.
All you can do is be armed with an understanding of the basic issues, vec-
torization, and the memory aspects explained next and then try various
approaches.
Performance Enhancement: Speed and Memory 313