我的代码遇到了我可以在此代码段中重现的性能障碍
rm (z)
z = c()
system.time({z[as.character(1:10^5)] = T})
user system elapsed
48.716 0.023 48.738
我尝试用
预先分配zz = 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。感谢
安东尼奥
答案 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)]
将涉及索引查找的名称,无法回退到一次追加项目,等等。