2013-05-17 40 views
4

我的代码时,无名(变得命名)向量创下了性能障碍,我可以重现这个片段很慢分配中的R

rm (z) 
z = c() 
system.time({z[as.character(1:10^5)] = T}) 
user system elapsed 
48.716 0.023 48.738 

我试着用

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狮子与R 15.2。由于

安东尼

回答

3

这似乎很有趣。它看起来似乎是R为每个不匹配的名称一次扩展向量一个元素。在这里,我们(一)只选择最后一个值,如果名称是重复的,然后(二)更新现有命名的元素和(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 

在更新(用“最后”值)命名矢量

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

同时又有上?"[<-"如提及的是零长度字符串“”是匹配。

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

TRUE FALSE 
+0

我没有去找出涉及的源代码,但是其他实验支持这种解释。幸运的是,我发现了一种不需要命名向量的不同方法。 – piccolbo

-1

要解决这个问题(一般),您可以脱钩任务命名:

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

但我真的不知道为什么会发生衰退(这听起来像全as.character是在你的表达式中要求z的每个元素,但这只是一个猜测)。

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


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

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

是。我刚到同一个地方。从看长度看,应该是显而易见的。具体来说,'x < - 1:5; x ['a'] < - 6'扩展了'x'。 – joran

+0

那么你为什么认为我问这个问题是否是解决方案? – piccolbo

-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)]将涉及名称到索引查找,失败回落到一次附加项目,等等。