在R中,为什么v [length(v)+1] = x比c(v,x)更好?

Bil*_*ill 8 r

我编写了以下两个函数,一个用于使用连接测试增加的向量大小,另一个用括号测试:

c_test <- function(n) {
  cv = c()
  for(i in 1:n) cv = c(cv, i)
  cv
  }

b_test <- function(n) {
  bv = c()
  for (i in 1:n) bv[i] = i
  bv
  }

library(microbenchmark)
microbenchmark(c_test(1e+4), b_test(1e+4), times = 100)

#Unit: milliseconds
#          expr       min        lq      mean    median        uq      max neval
# c_test(10000) 140.27923 145.73282 156.82319 148.16175 151.74713 267.2393   100
# b_test(10000)  49.58033  54.42992  56.24268  54.86033  56.30862 132.8394   100
Run Code Online (Sandbox Code Playgroud)

这是一个很大的时间差异,我不明白为什么使用括号比使用连接更好.在这两种情况下似乎都需要时间来分配新的内存,但这似乎并不正确.我还认为它可能会c(v, x)转换x为与v合并之前相同的类型,但是说v[i] = as.vector(x)并不是一个重要的时间成本.

Pet*_*lis 3

这可能应该是一条评论,因为我不知道实际的答案,但它太长了。

“c”和“[”都是原始的、内部的和通用的。这意味着方法分派是由 C 函数完成的,这就是我所能回答的实际问题。那里发生了一些神秘的事情,在这个特定方面,“[”比“c”更有效。

然而,我确实想指出,根据一些但不是全部的评论,这两种方法都是低效的,而不仅仅是因为矢量化。根据您期望的向量大小预先分配内存空间确实有很大帮助,比c和之间的差异要大得多[。预分配可以使您的速度比该版本提高 70% 到 90% [

# very poor - repeated calls to c() to extend
c_test <- function(n) {
  cv = c()
  for(i in 1:n) cv = c(cv, i)
  cv
}

# slightly better - just use []
b_test <- function(n) {
  bv = c()
  for (i in 1:n) bv[i] = i
  bv
}

# much better practice - preallocate length of the vector
d_test <- function(n) {
  bv = numeric(n)
  for (i in 1:n) bv[i] = i
  bv
}

# good practice if possible - vectorisation
e_test <- function(n) {
  bv = 1:n
  bv
}


library(microbenchmark)
microbenchmark(c_test(1e+4), b_test(1e+4), d_test(1e+4), e_test(1e+4), times = 100)
Run Code Online (Sandbox Code Playgroud)

这给出:

Unit: microseconds
          expr        min         lq         mean     median         uq        max neval cld
 c_test(10000) 102355.753 111202.568 129250.53638 114237.234 132468.938 220005.926   100   c
 b_test(10000)  47337.481  52820.938  77029.01728  59450.864 116529.185 192643.555   100  b 
 d_test(10000)   6761.877   7492.741   7965.37288   7814.519   8353.778  11007.605   100 a  
 e_test(10000)      3.555      6.321      9.32347      8.692     10.272     27.259   100 a  
Run Code Online (Sandbox Code Playgroud)

另外,正如 @Roland 所说,“随着尺寸的增加,生长一个物体会变得更加昂贵”。随着向量变大,内存中可用于放置向量的位置就会越来越少。

我明白e_test(矢量化)不适用于您的斐波那契用例,但无论如何将其保留下来进行比较,以便了解可以进行矢量化时的加速程度。

  • 如果您查看[do_c的代码](https://github.com/wch/r-source/blob/trunk/src/main/bind.c),您会看到一条注释,说明所有参数都被评估并检查方法派遣。对于子集化,检查要简单得多,因为只需要检查一个参数。另外,我对 OP 的断言提出质疑,即他们无法在用例中进行预分配。您始终可以猜测大小并根据需要分块增长。我最近写了一个斐波那契实现,OP可能感兴趣:http://stackoverflow.com/a/41323414/1412059 (2认同)