4

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

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

平台是带有 R 15.2 的 OS X mt lion。谢谢

安东尼奥

4

4 回答 4

4

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

以下是三个相关的运行:

# 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)
于 2013-05-17T18:37:02.620 回答
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

在更新(使用“最后一个”值)命名向量时

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

并且如前所述?"[<-",零长度字符串 ""匹配。

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

 TRUE FALSE
于 2013-05-18T06:39:07.650 回答
-1

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

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

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

于 2013-05-17T18:30:48.500 回答
-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)]将涉及名称到索引的查找,如果失败则回退到一次项追加,等等。

于 2013-05-17T18:43:44.943 回答