2017-01-13 2 views
8

私はブラケットを使用して増加し、ベクトルCONCATENATEを使用してサイズ、およびいずれかをテストするために次の2つの関数、1を書いた:Rでは、v [length(v)+1] = xがc(v、x)よりも優れているのはなぜですか?

c_test <- function(n) { 
    cv = c() 
    for(i in 1:n) cv = c(cv, i) 
    cv 
    } 

b_test <- function(n) { 
    bv = c() 
    for (i in 1:n) bv[i] = i 
    bv 
    } 

library(microbenchmark) 
microbenchmark(c_test(1e+4), b_test(1e+4), times = 100) 

#Unit: milliseconds 
#   expr  min  lq  mean median  uq  max neval 
# c_test(10000) 140.27923 145.73282 156.82319 148.16175 151.74713 267.2393 100 
# b_test(10000) 49.58033 54.42992 56.24268 54.86033 56.30862 132.8394 100 

これは大きな時間差で、ブラケットを使用することはそんなに優れている理由を私は理解していません連結を使用するよりも。どちらの場合も新しいメモリを割り当てるのに時間がかかるようですが、これは当てはまりません。また、c(v, x)xをマージする前にvと同じタイプに変換していますが、v[i] = as.vector(x)と言ってもそれほど時間がかかりません。

+0

ここで注意すべき点は、この動作が実際に何が起こっているのかを示唆する 'my_len'の値が低くなるためです。後者は、ベクターが成長するためにある程度の余分なスペースをあらかじめ割り当てるように見えますが、前者は常にコピーを作成します。事前割り当ては、コピーよりも小さいサイズでは遅くなります。 – Shape

+0

実際には、800-900の範囲のマイレン(mylen)の右にフリップされます – Shape

+0

事前割り当てなしで 'for'ループで使用されると、どちらも悪いです。 Rは、これらの時間のペナルティを避けるためにベクトル化された関数の周りに構築されています。 – alistaire

答えて

3

実際の回答がわからないので、これはおそらくコメントになるはずですが、長すぎます。

"c"と "["はプリミティブ、内部および汎用の両方です。つまり、メソッドのディスパッチはC関数によって行われます。これは、実際の質問に答えるためのものです。何か不思議なことが起こっていて、この特定の点では "["は "c"よりも効率的です。

しかし、コメントのすべてではないが、これらの方法の両方が、ベクトル化だけでなく、非効率的であることを指摘したいと思います。あなたが期待しているベクトルのサイズのメモリ空間をあらかじめ割り当てておくことは、c[の違いよりもはるかに大いに役立ちます。

# very poor - repeated calls to c() to extend 
c_test <- function(n) { 
    cv = c() 
    for(i in 1:n) cv = c(cv, i) 
    cv 
} 

# slightly better - just use [] 
b_test <- function(n) { 
    bv = c() 
    for (i in 1:n) bv[i] = i 
    bv 
} 

# much better practice - preallocate length of the vector 
d_test <- function(n) { 
    bv = numeric(n) 
    for (i in 1:n) bv[i] = i 
    bv 
} 

# good practice if possible - vectorisation 
e_test <- function(n) { 
    bv = 1:n 
    bv 
} 


library(microbenchmark) 
microbenchmark(c_test(1e+4), b_test(1e+4), d_test(1e+4), e_test(1e+4), times = 100) 

これが与える:また

Unit: microseconds 
      expr  min   lq   mean  median   uq  max neval cld 
c_test(10000) 102355.753 111202.568 129250.53638 114237.234 132468.938 220005.926 100 c 
b_test(10000) 47337.481 52820.938 77029.01728 59450.864 116529.185 192643.555 100 b 
d_test(10000) 6761.877 7492.741 7965.37288 7814.519 8353.778 11007.605 100 a 
e_test(10000)  3.555  6.321  9.32347  8.692  10.272  27.259 100 a 

、@Rolandが言ったように「オブジェクトはサイズが大きくなると、より高価な取得高まっ」事前割り当ては、あなたに[バージョンから70%〜90%のスピードアップを与えます。ベクトルが大きくなるにつれて、メモリ内で利用可能なスポットが少なくなります。

(ベクター化)は、フィボナッチの使用例では適用できませんが、ベクトル化が可能な場合の速度のスケールを考えるために、比較のために残しておきたいと思います。

+2

[do_c](https://github.com/wch/r-source/blob/trunk/src/main/bind.c)のコードを見ると、すべての引数が評価され、チェックされているというコメントが表示されますメソッドディスパッチ。サブセッティングの場合、チェックする必要がある引数は1つだけなので、チェックははるかに簡単です。また、OPのアサーションには、ユースケースにあらかじめ割り当てることができないと主張します。あなたはいつもサイズを推測し、必要に応じてチャンクで成長することができます。私は最近、OPに興味のあるフィボナッチの実装を書いています:http://stackoverflow.com/a/41323414/1412059 – Roland

+0

ありがとう@ロランド、非常に便利なre do_c。私は実際に事前配分に同意します。 forループがあるなら、明らかにそれを行うことができます。 whileループの場合は、まだ推測できます。 –

+1

@Rolandここでは事前配分ができないという印象を与えても、私はそれを言っているわけではありません。私は個人的にはほとんどの時間の事前割り当てを避けるが、愚かな理由のためにのみ(恥ずかしい、私が知っている)。私はRで始まったばかりなので、フィボナッチのコードは便利です。私はmemoizationの実装方法が不思議だったので、関数が入れ子になっているのは興味深いことです。 – Bill

関連する問題