为什么sapply比样本大小的循环慢?

Bri*_*ers 5 benchmarking r

所以假设我想取向量X = 2*1:N并将e提高到每个元素的指数.(是的,我认识到这样做的最好方法是简单地通过向量化exp(X),但重点是将循环与sapply进行比较).好吧,我通过逐步尝试三个方法(一个用于for循环,两个用不同的方式应用了sapply)和不同的样本大小并测量相应的时间来测试.然后,我绘制每种方法的样本大小N对时间t.

每种方法用"#####"表示.

k <- 20 
t1 <- rep(0,k) 
t2 <- rep(0,k)
t3 <- rep(0,k)
L <- round(10^seq(4,7,length=k))


for (i in 1:k) {
  X <- 2*1:L[i]
  Y1 <- rep(0,L[i])
  t <- system.time(for (j in 1:L[i]) Y1[j] <- exp(X[j]))[3] #####
  t1[i] <- t
}

for (i in 1:k) {
  X <- 2*1:L[i]
  t <- system.time( Y2 <- sapply(1:L[i], function(q) exp(X[q])) )[3] #####
  t2[i] <- t
}

for (i in 1:k) {
  X <- 2*1:L[i]
  t <- system.time( Y3 <- sapply(X, function(x) exp(x)) )[3] #####
  t3[i] <- t
}

plot(L, t3, type='l', col='green')
lines(L, t2,col='red')
lines(L, t1,col='blue')

plot(log(L), log(t1), type='l', col='blue')
lines(log(L), log(t2),col='red')
lines(log(L), log(t3), col='green')
Run Code Online (Sandbox Code Playgroud)

我们得到以下结果.N vs t的情节: 在此输入图像描述

log(N)vs log(t)的图 在此输入图像描述

蓝色图是for循环方法,红色和绿色图是sapply方法.在常规图中,您可以看到,随着样本大小变大,for循环方法比sapply方法更受青睐,这根本不是我所期望的.如果你看一下log-log图(为了更容易区分较小的N结果),我们看到sapply的预期结果比小N的循环更有效.

有没有人知道为什么sapply比样本大小的循环更慢?谢谢.

Jos*_*ich 4

您没有考虑为结果向量分配空间所需的时间Y1。随着样本大小的增加,分配所花费的时间Y1在执行时间中所占的份额变得更大,而替换所花费的时间所占的份额变得更小。

sapply总是为结果分配内存,因此这就是随着样本大小的增加而效率降低的原因之一。gagolewssapply对于调用也有一个很好的观点simplify2array。这(可能)会添加另一个副本。


经过更多测试后,随着lapply对象变大,它看起来仍然与包含 for 循环的字节编译函数大致相同或更慢。我不知道如何解释这一点,除了可能的这一行do_lapply

if (MAYBE_REFERENCED(tmp)) tmp = lazy_duplicate(tmp);
Run Code Online (Sandbox Code Playgroud)

或者可能是如何lapply构造函数调用的东西......但我主要是猜测。

这是我用来测试的代码:

k <- 20 
t1 <- rep(0,k) 
t2 <- rep(0,k)
t3 <- rep(0,k)
L <- round(10^seq(4,7,length=k))
L <- round(10^seq(4,6,length=k))

# put the loop in a function
fun <- function(X, L) {
  Y1 <- rep(0,L)
  for (j in 1:L)
    Y1[j] <- exp(X[j])
  Y1
}
# for loops often benefit from compiling
library(compiler)
cfun <- cmpfun(fun)

for (i in 1:k) {
  X <- 2*1:L[i]
  t1[i] <- system.time( Y1 <- fun(X, L[i]) )[3]
}
for (i in 1:k) {
  X <- 2*1:L[i]
  t2[i] <- system.time( Y2 <- cfun(X, L[i]) )[3]
}
for (i in 1:k) {
  X <- 2*1:L[i]
  t3[i] <- system.time( Y3 <- lapply(X, exp) )[3]
}
identical(Y1, Y2)          # TRUE
identical(Y1, unlist(Y3))  # TRUE
plot(L, t1, type='l', col='blue', log="xy", ylim=range(t1,t2,t3))
lines(L, t2, col='red')
lines(L, t3, col='green')
Run Code Online (Sandbox Code Playgroud)