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