为什么使用“[[ ]]”方法来对列表进行子集操作比使用“$?”更快?

43

我一直在做一些需要对列表进行大量子集操作的项目,通过对代码进行剖析,我发现 object[["nameHere"]] 这种方式通常比 object$nameHere 更快。

以一个创建了具有命名组件的列表为例:

a.long.list <- as.list(rep(1:1000))
names(a.long.list) <- paste0("something",1:1000)

为什么会这样:

system.time (
for (i in 1:10000) {
    a.long.list[["something997"]]
}
)


user  system elapsed 
0.15    0.00    0.16 

比这个速度更快:

system.time (
    for (i in 1:10000) {
        a.long.list$something997
    }
)

user  system elapsed 
0.23    0.00    0.23 

我想知道这种行为是否普遍存在,以及我是否应该尽可能避免使用 $ subset 或者最有效的选择取决于其他因素?


9
我猜想这可能与 $ 符号的部分匹配有关。假设你有一个列表 my_list <- list("a" = 1, "ace" = 2)。如果你尝试 my_list$ac,它会得到 ace,但如果你尝试 my_list[["ac"]],它会找不到任何东西。 - Frank
5
不排除部分匹配理论的可能性,但我希望完整的答案能够解释为什么在 OP 的例子中,在 [[ 中添加 exact = FALSE 不会降低性能。 - flodel
7
值得一提的是,$[[是由两个完全不同的C函数实现的(均在src/main/subset.c中)。对于$,相关函数是do_subset3,该函数又调用R_subset3_dflt。而[[则使用另一个函数do_subset2,该函数又调用do_subset2_dflt - Josh O'Brien
5
do_subset2之前的注释简单地提到:“[[子集运算符。需要快速执行。” - Josh O'Brien
3
可能值得一提的是R 3.0.0中的最新变化之一:在数据框上使用$运算符进行部分匹配现在会发出警告,并可能在未来成为无效操作。 如果打算进行部分匹配,请使用foo[["bar", exact = FALSE]]替换foo$bar。 - zap2008
显示剩余3条评论
1个回答

11

函数[[首先尝试进行完全匹配,如果不行,再尝试进行部分匹配。而$函数会依次对每个元素进行完全和部分匹配。如果执行以下操作:

system.time (
    for (i in 1:10000) {
     a.long.list[["something9973", exact=FALSE]]
     }
)
即,如果您在没有完全匹配的情况下运行部分匹配,您会发现$实际上略微更快。

我认为这回答了Flodel的澄清问题,即为什么添加exact = FALSE不会降低性能。无论如何,我现在相信,在速度很重要的编程环境中,使用[[会更好,除非存在需要部分匹配的高概率情况(这通常会在我的程序中引入错误而不是解决问题)。 - Jon M
1
顺便提一下,如果要在一个包含10000个元素的列表中获得>100倍的性能,则可以使用as.environment(a.long.list)将列表转换并在其中执行查找。环境被实现为哈希映射,具有几乎恒定的查找时间。线性列表查找随着大小的增加而变得越来越慢(元素在列表中的位置越靠后)。 - Soren Havelund Welling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接