为什么预分配对列表有用?

时间:2013-08-25 00:52:36

标签: r

我理解预分配矢量或矩阵很有用,因为它们总是存储在连续的内存块中。

但是,就列表而言,它可以包含差异长度和模式的元素。所以我的第一个猜测是列表可能只包含指向其元素真实地址的指针。我对么?这里的一个相关问题What is the internal implementation of lists?它说列表本质上是一个数组,但它不包括元素如何存储在列表中,而每个元素的大小可能会改变。

示例1:如果列表包含a,b,c,d,e,并且myList$a<-1:1000000,是否已修改列表(这意味着仅更新a)或整个列表被复制和更新?

示例2

> system.time( { myList <- list()
+                myList$a <- 1:10000000
+                myList$b <- 1:10000100
+                myList$c <- 1:10000200 
+                myList$d <- 1:10000300})
   user  system elapsed 
   0.01    0.02    0.03

> system.time({ myList2<-list(a=1:10000000,b=1:10000100,c=1:10000200,d=1:10000300) })
   user  system elapsed 
   0.00    0.03    0.03 
由于没有预分配,

myListmyList2相比效率会非常低吗?或者没有明显的性能差异,无论a,b,c,d有多大,因为第一个只复制指针几倍?

这是预分配。列表看起来如何?它是否仅为指针预分配内存?如果是这种情况,我认为没有任何用处,因为无论如何都没有太多的数据复制指针。

> system.time( { myList <- vector("list", 4)
+                myList[[1]] <- 1:10000000
+                myList[[2]] <- 1:10000100
+                myList[[3]]  <- 1:10000200 
+                myList[[4]] <- 1:10000300
+                names(myList) <- letters[1:4]})
   user  system elapsed 
   0.01    0.02    0.03 

2 个答案:

答案 0 :(得分:7)

 system.time( { myList <- list()
   myList$a <- 1:100000
   myList$b <- 1:100001
   myList$c <- 1:100002 
   myList$d <- 1:100003})
#   user  system elapsed 
#  0.019   0.001   0.022 

 system.time({ myList<-list(a=1:100000,b=1:100001,c=1:100002,d=1:100003) })
#   user  system elapsed 
#  0.001   0.001   0.002 

一般情况下建议预先分配,尽管你所做的并不像我认为的预分配。这就是我给出的描述。

system.time( { myList <- vector("list", 4)
   myList[[1]] <- 1:100000
   myList[[2]] <- 1:100001
   myList[[3]]  <- 1:100002 
   myList[[4]] <- 1:100003
   names(myList) <- letters[1:4]})
#   user  system elapsed 
#  0.001   0.001   0.001 
BTW:我不认为列表的心理模型只是一个指针是有用的。很多时候,简单的赋值会创建一个全新的临时副本,然后重新分配该名称。

答案 1 :(得分:7)

这对评论来说太长了 - 但不是一个完整的答案。

在修改时,命名列表与未命名列表的处理方式不同。

复制(对于大型对象)

这是一个复杂的问题。请参阅http://radfordneal.wordpress.com/2013/07/02/fixing-rs-named-problems-in-pqr/以获得合理的解释,并注意其中一些更改正在R的开发版本中实现。另请参阅http://r.789695.n4.nabble.com/Confused-about-NAMED-td4103326.html以了解对底层复杂性的更多了解

以下是各种预分配可能性的时间安排

# preallocated contents so timing is list related only
.a <- seq_len(1e6); .b <- seq_len((1e6 + 1))
.c <- seq_len((1e6 + 2)); .d <- seq_len((1e6 + 3))



f1 <- function(){
# using `$<-` empty list
  x <- list()
  x$a <- .a
  x$b <- .b
  x$c <- .c 
  x$d <- .d
  x
}


f2 <- function(){
  # using `[[<-` on empty list
  x <- list()
  x[['a']] <- .a
  x[['b']] <- .b
  x[['c']] <- .c
  x[['d']] <- .d
  x
}


f3 <- function(){
  # using `[[<-` on empty list, naming later
  x <- list()
  x[[1]] <- .a
  x[[2]] <- .b
  x[[3]] <- .c
  x[[4]] <- .d
  names(x) <- letters[1:4]
  x
}

f4 <- function(){ 
  # just define the list
  x <- list(a = .a, b = .b,
            c = .c, d = .d)
}


f5 <- function(){ 
  # create a list of length 4, then fill and name
  x <- vector(mode = 'list', length = 4)
  x[[1]] <- .a
  x[[2]] <- .b
  x[[3]] <- .c
  x[[4]] <- .d
  names(x) <- letters[1:4]
  x
}

# f6 <- function(){
#  # this doesn't work!
#  # it creates a list of length 8
#  x <- vector(mode = 'list', length = 4)
#  x[['a']] <- .a
#  x[['b']] <- .b
#  x[['c']] <- .c
#  x[['d']] <- .d
#  x
# }


f7 <-function(){
# pre allocate list, name then fill
x <- vector(mode = 'list', length = 4)
  names(x) <- letters[1:4]
  x[[1]] <- .a
  x[[2]] <- .b
  x[[3]] <- .c
  x[[4]] <- .d
  x
}

f8 <- function(){
# preallocate correct length and then name
# and fill by name
  x <- vector(mode = 'list', length = 4)
  names(x) <- letters[1:4]
  x[['a']] <- .a
  x[['b']] <- .b
  x[['c']] <- .c
  x[['d']] <- .d
  x
}

library(microbenchmark)
microbenchmark(f1(),f2(),f3(),f4(),f5(),f7(),f8(),times=100)


microbenchmark(f1(),f2(),f3(),f4(),f5(),f7(),f8(),times=100)
# Unit: microseconds
#   expr      min       lq   median       uq       max neval
#   f1()    6.038   11.169   12.980   14.791    34.110   100
#   f2() 2528.881 4387.962 4707.014 6472.823 74586.266   100
#   f3() 2532.805 4537.376 4714.258 5353.722 74903.206   100
#   f4() 2475.756 4531.489 4721.503 6331.860 74460.395   100
#   f5() 2508.959 4512.474 4759.535 6673.551  7966.668   100
#   f7() 2545.181 4477.761 4709.127 6372.610  7964.856   100
#   f8() 2508.053 4467.799 4669.131 6181.993 74236.726   100
#
#  All results are identical.
all(identical(f1(),f2()),identical(f1(),f3()),
    identical(f1(),f4()),identical(f1(),f5()),
    identical(f1(),f7()),identical(f1(),f8()))

#[1] TRUE

所有结果都相同。

显然在空列表中使用$&lt; -`是明显的赢家。这违背了我应该最快的想法