我理解预分配矢量或矩阵很有用,因为它们总是存储在连续的内存块中。
但是,就列表而言,它可以包含差异长度和模式的元素。所以我的第一个猜测是列表可能只包含指向其元素真实地址的指针。我对么?这里的一个相关问题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
由于没有预分配, myList
与myList2
相比效率会非常低吗?或者没有明显的性能差异,无论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
答案 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; -`是明显的赢家。这违背了我应该最快的想法