Performance 在R中未命名(变为命名)时对向量的赋值非常慢

Performance 在R中未命名(变为命名)时对向量的赋值非常慢,performance,r,vector,Performance,R,Vector,我的代码遇到了性能障碍,我可以在这段代码中重现 rm (z) z = c() system.time({z[as.character(1:10^5)] = T}) user system elapsed 48.716 0.023 48.738 我试着预先分配z z = logical(10^5) 但这没什么区别。 然后,我用 names(z) = character(10^5) 仍然没有速度差 system.time({z[as.character(1:10^5)] = T})

我的代码遇到了性能障碍,我可以在这段代码中重现

rm (z)
z = c()
system.time({z[as.character(1:10^5)] = T})
user  system elapsed 
48.716   0.023  48.738 
我试着预先分配z

z = logical(10^5)
但这没什么区别。 然后,我用

names(z) = character(10^5)
仍然没有速度差

system.time({z[as.character(1:10^5)] = T})
user  system elapsed 
50.345   0.035  50.381 
如果我重复测试,无论有没有预分配,速度都会回到合理水平(快100倍以上)

最后,我发现了一个不太好的解决方法:

names(z) = as.character(1:10^5)
system.time({z[as.character(1:10^5)] = T})
user  system elapsed 
0.035   0.001   0.035 
要回到慢时间,您可以使用不同的方式rm(z)并初始化它,但即使将名称更改回其他名称,也会使时间变慢。 我是说这不是一个很好的解决办法,因为我不明白它为什么会起作用,所以很难将它推广到我事先不知道名称的实际用例中。当然,考虑到两个数量级的差异,有人怀疑涉及到一些非矢量化或解释器繁重的操作,但您可以看到我的代码是无循环的,并且不会调用我能想到的任何解释代码。然后尝试使用更小的向量,我发现执行时间的增长比线性的,可能是二次的快得多,这意味着其他的东西。问题是,这种速度行为的原因是什么,以及使其更快的解决方案是什么

平台是带有R15.2的OS X mt lion。谢谢

Antonio

要解决此问题(一般而言),您可以将命名与分配分离:

z[1:10^5] = T
names(z) = as.character(1:10^5)

但是我真的不知道为什么会发生减速(听起来像是对表达式中的
z
的每个元素调用了完整的
as.character
,但这只是一个猜测)。

我可以推测到底发生了什么,因为下面的计时似乎符合我的假设

以下是三个相关运行:

# run 1 - slow
rm (z)
n <- 3*10^4
z <- vector("logical", n)
system.time({
z[as.character(1:n)] <- T
})
#    user  system elapsed 
#    5.08    0.00    5.10

# run 2 - fast
rm (z)
n <- 3*10^4
z <- vector("logical", n)
system.time({
names(z) <- as.character(1:n)
z[as.character(1:n)] <- T
})
#    user  system elapsed 
#    0.03    0.00    0.03 

# run 3 - slow again
rm (z)
n <- 3*10^4
z <- vector("logical", n)
system.time({
for (i in 1:n) names(z)[i] <- as.character(i)
z[as.character(1:n)] <- T
})
#    user  system elapsed 
#    6.10    0.00    6.09 

我不能完全指出这一点,但我怀疑简化一个例子可能有助于解释一些事情:

R> z = logical(6); z[1:3] = T; z[as.character(1:3)] = T; z
                                        1     2     3
 TRUE  TRUE  TRUE FALSE FALSE FALSE  TRUE  TRUE  TRUE

此外,虽然
z[1:5]
可能是直接的,可能是矢量化的,但查找
z[as.character(1:5)]
将涉及到从名称到索引的查找,失败时返回到每次追加项,等等。

这似乎很有趣。对于每个不匹配的名称,R似乎一次只扩展一个元素。在这里,我们(a)仅选择最后一个值,以防名称重复,然后(b)更新现有命名元素,并(c)追加新元素

updateNamed <-
    function(z, z1)
{
    z1 <- z1[!duplicated(names(z1), fromLast=TRUE)] # last value of any dup
    idx <- names(z1) %in% names(z)                  # existing names...
    z[ names(z1)[idx] ] <- z1[idx]                  # ...updated
    c(z, z1[!idx])                                  # new names appended
}
更新(使用“last”值)命名向量时

> length(updateNamed(z, z1))
[1] 60000
> length(updateNamed(z1, !z1))
[1] 30000

以及《代码》中提到的内容。”[是的。我刚刚到了同一个地方。从长度上看,这应该是显而易见的。具体来说,
x那么你认为我为什么会问#2是否是一个解决方案?我没有找到涉及的源代码,但额外的实验支持这种解释。幸运的是,我找到了一种不需要n的不同方法阿米德向量。
> z <- setNames(logical(2), c("a", 2))
> updateNamed(z, setNames(c(TRUE, FALSE, TRUE, FALSE), c("a", 2, 2, "c")))
    a     2     c
 TRUE  TRUE FALSE   
> n <- 3*10^4
> z <- logical(n)
> z1 <- setNames(rep(TRUE, n), as.character(1:n))
> system.time(updateNamed(z, z1))
   user  system elapsed
  0.036   0.000   0.037
> length(updateNamed(z, z1))
[1] 60000
> length(updateNamed(z1, !z1))
[1] 30000
> z = TRUE; z[""] = FALSE; z

 TRUE FALSE