2015-05-12 40 views
14

,我做了一些優化,通過從一個工序......性能.Primitive和。內部

> library(microbenchmark) 
> microbenchmark(paste0("this","and","that")) 
Unit: microseconds 
          expr min lq mean median uq max neval 
paste0("this", "and", "that") 2.026 2.027 3.50933 2.431 2.837 34.038 100 

> microbenchmark(.Internal(paste0(list("this","and","that"),NULL))) 
Unit: microseconds 
               expr min lq mean median uq max neval 
.Internal(paste0(list("this", "and", "that"), NULL)) 1.216 1.621 2.77596 2.026 2.027 43.764 100 

到目前爲止好...

但後來經過我注意到,list被定義爲

function (...) .Primitive("list") 

我試圖進一步 「簡化」

> microbenchmark(.Internal(paste0(.Primitive("list")("this","and","that"),NULL))) 
Unit: microseconds 
                   expr min lq mean median uq max neval 
.Internal(paste0(.Primitive("list")("this", "and", "that"), NULL)) 3.241 3.242 4.66433 3.647 3.648 80.638 100 

和時間增加!

我的猜測是,處理字符串"list"是問題的根源,而且它的處理方式不同功能list

但如何實際通話中?

免責聲明:我知道這會傷害可讀性而不僅僅是幫助性能。這只是一些非常簡單的功能,不會改變,而且經常使用,即使在這種成本下也需要輕微的性能問題。


編輯迴應喬希·奧布萊恩的評論:

我不知道這是什麼說關於他的想法,但是從微基準產生

library(compiler) 
ff <- compile(function(...){.Internal(paste0(.Primitive("list")("this","and","that"),NULL))}) 
ff2 <- compile(function(...){.Internal(paste0(list("this","and","that"),NULL))}) 
microbenchmark(eval(ff),eval(ff2),times=10000) 
> microbenchmark(eval(ff2),eval(ff),times=10000) 
Unit: microseconds 
     expr min lq  mean median uq  max neval 
eval(ff2) 1.621 2.026 2.356761 2.026 2.431 144.257 10000 
    eval(ff) 1.621 2.026 2.455913 2.026 2.431 89.148 10000 

,看着劇情(只是用plot()包裝它看看它自己)運行了很多次,看起來那些在統計上相同的性能,儘管看起來像ff2的「最大」值具有更糟糕的最壞情況。不知道該怎麼做,但也許這會幫助某人。所以基本上說,他們編譯爲相同的代碼。這是否意味着他的評論是答案?

+4

難道是由於事實的'基地::名單身()'是字節編譯,而你的最後一個塊需要一個函數調用的評估('.Primitive(「名單」)')通過評估符號「list」來達到同樣的目的?這只是我的猜測,但似乎字節編譯的一個優點是R實際上不需要評估對'.Primitive(「。list」)的調用,以找到每個C代碼的入口點,並且每次使用函數'list' ... –

+1

由於計算機上的一些隨機事件導致等待資源或某個守護進程臨時阻塞RAM,「最大」情況通常是一次性異常值等。 –

+0

@ JoshO'Brien:我不明白你的意見。 'base :: list'是一個基元,被定義爲'function(...).Primitive(「list」)',因此沒有字節編譯的主體。 –

回答

11

原因.Internal(paste0(.Primitive("list")("this","and","that"),NULL))較慢似乎是因爲Josh O'Brien猜到了什麼。調用.Primitive("list")會直接導致一些額外開銷。

您可以通過一個簡單的例子看到效果:

require(compiler) 
pl <- cmpfun({.Primitive("list")}) 
microbenchmark(list(), .Primitive("list")(), pl()) 
# Unit: nanoseconds 
#     expr min  lq median  uq max neval 
#    list() 63 98.0 112.0 140.5 529 100 
# .Primitive("list")() 4243 4391.5 4486.5 4606.0 16077 100 
#     pl() 79 135.5 148.0 175.5 39108 100 

這就是說,你不會要能夠從R迅速提高.Primitive.Internal速度。它們都是C代碼的入口點。

而且沒有理由嘗試用.Internal替換.Primitive的呼叫。這是遞歸的,因爲.Internal本身就是一個原語。

> .Internal 
function (call) .Primitive(".Internal") 

如果嘗試.Internal「直接」叫你會得到相同的緩慢...和類似的「加速」如果你編譯了「直接」的呼叫。

Internal. <- function() .Internal(paste0(list("this","and","that"),NULL)) 
Primitive. <- function() .Primitive(".Internal")(paste0("this","and","that"),NULL) 
cPrimitive. <- cmpfun({Primitive.}) 
microbenchmark(Internal., Primitive., cPrimitive., times=1e4) 
# Unit: nanoseconds 
#   expr min lq median uq max neval 
# Internal. 26 27  27 28 1057 10000 
# Primitive. 28 32  32 33 2526 10000 
# cPrimitive. 26 27  27 27 1706 10000 
0

將R解釋已經硬編碼爲常用功能的優化,而這遠不止字節編譯更深:

> list2 <- list 
> list3 <- cmpfun(list2) 
> microbenchmark(
+ list(1,2), 
+ list2(1,2), 
+ list3(1,2) 
+) 
Unit: nanoseconds 
     expr min lq mean median uq max neval 
    list(1, 2) 576 620.5 654.53 640.0 675.5 941 100 
list2(1, 2) 619 702.0 1123.43 728.0 761.0 39045 100 
list3(1, 2) 617 683.0 735.83 715.5 759.0 1964 100 

這裏的SEXPs樣子。請注意 「列表」

> .Internal(inspect(quote(list(1,2)))) 
@23b0ed0 06 LANGSXP g0c0 [NAM(2)] 
    @1ed8f48 01 SYMSXP g1c0 [MARK,LCK,gp=0x4000] "list" (has value) 
    @2c7adf8 14 REALSXP g0c1 [] (len=1, tl=0) 1 
    @2c7adc8 14 REALSXP g0c1 [] (len=1, tl=0) 2 

list2元數據缺少一些元數據:

> list2 <- list 
> .Internal(inspect(quote(list2(1,2)))) 
@23b1578 06 LANGSXP g0c0 [NAM(2)] 
    @23b0a70 01 SYMSXP g0c0 [] "list2" 
    @2c7ad08 14 REALSXP g0c1 [] (len=1, tl=0) 1 
    @2c7acd8 14 REALSXP g0c1 [] (len=1, tl=0) 2 

.Primitive("list")是一個更復雜的表達式:

> .Internal(inspect(quote(.Primitive("list")(1,2)))) 
@297e748 06 LANGSXP g0c0 [NAM(2)] 
    @297d9a0 06 LANGSXP g0c0 [] 
    @1ec4530 01 SYMSXP g1c0 [MARK,LCK,gp=0x4000] ".Primitive" (has value) 
    @2c7a888 16 STRSXP g0c1 [] (len=1, tl=0) 
     @1ed5588 09 CHARSXP g1c1 [MARK,gp=0x61] [ASCII] [cached] "list" 
    @2c7a858 14 REALSXP g0c1 [] (len=1, tl=0) 1 
    @2c7a828 14 REALSXP g0c1 [] (len=1, tl=0) 2 
+0

嗯。我不認爲這些基準測試顯示出明顯的差異,特別是因爲'list2'與'list'完全相同。 'list2'不是'list'的副本:它們都指向相同的位置,可以通過執行'list2 < - list; 。內部(檢查(列表)); 。內部(檢查(列表2))'。 –

+0

親自試一試:)我認爲區別在於代碼的解釋,而不是代碼本身。 也到這裏看看:https://github.com/wch/r-source/blob/565868293e125eb1a4f68fa149e2d24963edf781/src/main/eval.c#L5351-L5389 'list2'是一個變量,有被搜索從全球上來; 'list'是內建的,可以從內建表中取出。 –

+0

我曾嘗試過,出於某種原因(?)得到警告信息,例如「無法測量949次評估的積極執行時間」,以及高度可變的結果,有時會顯示一個結果,有時另一個顯示結果。也就是說,我不知道在不同位置查找變量的速度。我的第一個天真的想法*會是''.GlobalEnv'中的符號會比'「package:base」'中的搜索路徑的末尾找到更快(如果完全不同的話)。但是,我承認,在R的操作的這些C級詳細信息上我的頭腦很不好! –