2013-05-17 41 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)]將涉及名稱到索引查找,失敗回落到一次附加項目,等等。