在R中未命名(成名)时,向量的赋值非常慢

时间:2013-05-17 18:10:50

标签: 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})
user  system elapsed 
50.345   0.035  50.381 

如果我重复测试,无论有没有预先分配,速度都会恢复到合理水平(超过100倍)。

system.time({z[as.character(1:10^5)] = T})
user  system elapsed 
0.037   0.001   0.039 

最后,我找到了一个不太完美的解决方法:

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)并以不同的方式初始化它,但即使将名称更改回其他内容也会将时间翻转为慢速。 我说这不是一个解决方法,因为我不明白为什么它有效,所以很难推广到我事先不知道名字的实际用例。当然,考虑到两个数量级的差异,人们怀疑涉及一些非向量化或解释器操作,但是您可以看到我的代码是无循环的,并且不会调用我能想到的任何解释代码。然后尝试使用较小的向量,我看到执行时间比线性增长快得多,也许是二次,这指向其他东西。问题是这种速度行为的原因是什么,以及使其更快的解决方案是什么。

平台是OS X mt lion,R 15.2。感谢

安东尼奥

4 个答案:

答案 0 :(得分:3)

我可以猜测发生了什么,因为下面的时间似乎与我的假设一致。

以下是三个相关的运行:

# 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 

运行#3是我认为在后台发生的事情,或者至少是这样的事情:在按名称进行分配时,R一次查找一个名称,如果没有找到,则在名称向量的结尾。一次做这个就是杀了它......


您还指出,按照以下names(z) <- character(1:n)预先指定名称并没有帮助。呵呵,看到character(1:n)返回""所以它没有像你想象的那样设置名字。毫不奇怪,它没有多大帮助。您打算使用as.character代替character


最后,你问解决方案是什么让它更快?我说你已经找到了一个(Run#2)。你也可以这样做:

keys   <- as.character(1:n)
values <- rep(T, n)
z <- setNames(values, keys)

答案 1 :(得分:3)

这看起来很有趣。看起来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
}

哪个有效?

> 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

更新(使用'last'值)一个命名向量

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

以及?"[<-"中提到的零长度字符串“”匹配。

> z = TRUE; z[""] = FALSE; z

 TRUE FALSE

答案 2 :(得分:-1)

要解决此问题(通常),您可以将命名与赋值分离:

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

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

答案 3 :(得分:-1)

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

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)]将涉及索引查找的名称,无法回退到一次追加项目,等等。