「* apply」ファミリーは本当にベクトル化されていませんか?


138

したがって、私たちはすべてのRの新規ユーザーに「applyベクトル化されていません。パトリックバーンズRインフェルノサークル4をチェックしてください」と言っています(引用します)。

一般的な反射は、適用ファミリーで関数を使用することです。これは ベクトル化ではなく、ループ非表示です。apply関数の定義にはforループがあります。lapply関数はループを埋めますが、実行時間は明示的なforループとほぼ同じになる傾向があります。

実際、applyソースコードをざっと見てみると、ループがわかります。

grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] "        for (i in 1L:d2) {"  "    else for (i in 1L:d2) {"

これまでのところ、わかりましたが、見てみるとlapplyvapply実際にはまったく異なる画像が表示されます。

lapply
## function (X, FUN, ...) 
## {
##     FUN <- match.fun(FUN)
##     if (!is.vector(X) || is.object(X)) 
##        X <- as.list(X)
##     .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>

したがって、明らかにそこにfor隠れているR ループはなく、内部Cで作成された関数を呼び出しています。

うさぎの 穴をざっと見ると、ほとんど同じ画像が表示されます

さらに、colMeansベクトル化されていないと非難されたことのない関数を例にとってみましょう。

colMeans
# function (x, na.rm = FALSE, dims = 1L) 
# {
#   if (is.data.frame(x)) 
#     x <- as.matrix(x)
#   if (!is.array(x) || length(dn <- dim(x)) < 2L) 
#     stop("'x' must be an array of at least two dimensions")
#   if (dims < 1L || dims > length(dn) - 1L) 
#     stop("invalid 'dims'")
#   n <- prod(dn[1L:dims])
#   dn <- dn[-(1L:dims)]
#   z <- if (is.complex(x)) 
#     .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) * 
#     .Internal(colMeans(Im(x), n, prod(dn), na.rm))
#   else .Internal(colMeans(x, n, prod(dn), na.rm))
#   if (length(dn) > 1L) {
#     dim(z) <- dn
#     dimnames(z) <- dimnames(x)[-(1L:dims)]
#   }
#   else names(z) <- dimnames(x)[[dims + 1]]
#   z
# }
# <bytecode: 0x0000000008f89d20>
#   <environment: namespace:base>

えっ?それ.Internal(colMeans(...は、うさぎの穴からも見つかるだけです。それで、これはどう違うの.Internal(lapply(..ですか?

実際には迅速なベンチマークは、ことを明らかにsapplyより行いは悪くないcolMeansし、はるかに優れたよりfor大きなデータセットに対するループ

m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user  system elapsed 
# 1.69    0.03    1.73 
system.time(sapply(m, mean))
# user  system elapsed 
# 1.50    0.03    1.60 
system.time(apply(m, 2, mean))
# user  system elapsed 
# 3.84    0.03    3.90 
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user  system elapsed 
# 13.78    0.01   13.93 

言い換えれば、それはと言うことは正しいことlapplyvapply 、実際にベクトル化されている(比較にapplyなるforも呼び出すことループlapply)とパトリック・バーンズが本当に言って何を意味するのですか?


8
これはすべてセマンティクスですが、ベクトル化されているとは思いません。R関数が1回だけ呼び出され、値のベクトルを渡すことができる場合、ベクトル化されたアプローチを検討します。*apply関数はR関数を繰り返し呼び出し、これによりループが発生します。の優れたパフォーマンスについてsapply(m, mean):おそらく、Cコードlapplyはメソッドを1回だけディスパッチしてから、そのメソッドを繰り返し呼び出しますか?mean.defaultかなり最適化されています。
Roland

4
すばらしい質問です。基礎となるコードをチェックしていただきありがとうございます。最近変更されたかどうかを確認していましたが、バージョン2.13.0以降のRリリースノートではこれについて何もありません。
ilir 2015年

1
パフォーマンスは、プラットフォームと使用されるCコンパイラおよびリンカーフラグの両方にどの程度依存しますか?
smci 2015年

3
@DavidArenburg実際、明確に定義されているとは思いません。少なくとも私は正規の参照を知りません。言語の定義では「ベクトル化された」操作について言及していますが、ベクトル化については定義していません。
Roland

3
非常に関連している:Rの適用ファミリーは構文糖よりも多いですか?(そして、これらの回答と同様に、良い読み物でもあります。)
グレゴールトーマス

回答:


73

まず第一に、あなたの例では、あなたがのために公平ではない「data.frame」のテストを行いcolMeansapplyそして"[.data.frame"、彼らはオーバーヘッドを持っているので。

system.time(as.matrix(m))  #called by `colMeans` and `apply`
#   user  system elapsed 
#   1.03    0.00    1.05
system.time(for(i in 1:ncol(m)) m[, i])  #in the `for` loop
#   user  system elapsed 
#  12.93    0.01   13.07

マトリックスでは、画像は少し異なります。

mm = as.matrix(m)
system.time(colMeans(mm))
#   user  system elapsed 
#   0.01    0.00    0.01 
system.time(apply(mm, 2, mean))
#   user  system elapsed 
#   1.48    0.03    1.53 
system.time(for(i in 1:ncol(mm)) mean(mm[, i]))
#   user  system elapsed 
#   1.22    0.00    1.21

質問の主要な部分について言えば、lapply/ mapply/ etcと単純なRループの主な違いは、ループが行われる場所です。Rolandが指摘しているように、CループとRループはどちらも、最もコストのかかる各反復でR関数を評価する必要があります。本当に速いCの関数はCですべてを行う関数なので、これが「ベクトル化」とは何なのでしょうか。

「リスト」の各要素で平均を見つける例:

EDIT May 11 '16:「平均」を見つける例は、R関数を反復的に評価することとコンパイルされたコードとの違いの良い設定ではないと思います、(1)「数値」に関するRの平均アルゴリズムの特殊性のためsが単純でsum(x) / length(x)、(2)で「リスト」をテストする方が理にかなっているはずlength(x) >> lengths(x)です。したがって、「平均」の例を最後に移動して、別の例に置き換えます。)

簡単な例として、length == 1「リスト」の各要素の反対の結果を検討できます。

tmp.cファイル:

#include <R.h>
#define USE_RINTERNALS 
#include <Rinternals.h>
#include <Rdefines.h>

/* call a C function inside another */
double oppC(double x) { return(ISNAN(x) ? NA_REAL : -x); }
SEXP sapply_oppC(SEXP x)
{
    SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x)));
    for(int i = 0; i < LENGTH(x); i++) 
        REAL(ans)[i] = oppC(REAL(VECTOR_ELT(x, i))[0]);

    UNPROTECT(1);
    return(ans);
}

/* call an R function inside a C function;
 * will be used with 'f' as a closure and as a builtin */    
SEXP sapply_oppR(SEXP x, SEXP f)
{
    SEXP call = PROTECT(allocVector(LANGSXP, 2));
    SETCAR(call, install(CHAR(STRING_ELT(f, 0))));

    SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x)));     
    for(int i = 0; i < LENGTH(x); i++) { 
        SETCADR(call, VECTOR_ELT(x, i));
        REAL(ans)[i] = REAL(eval(call, R_GlobalEnv))[0];
    }

    UNPROTECT(2);
    return(ans);
}

そしてR側では:

system("R CMD SHLIB /home/~/tmp.c")
dyn.load("/home/~/tmp.so")

データあり:

set.seed(007)
myls = rep_len(as.list(c(NA, runif(3))), 1e7)

#a closure wrapper of `-`
oppR = function(x) -x

for_oppR = compiler::cmpfun(function(x, f)
{
    f = match.fun(f)  
    ans = numeric(length(x))
    for(i in seq_along(x)) ans[[i]] = f(x[[i]])
    return(ans)
})

ベンチマーク:

#call a C function iteratively
system.time({ sapplyC =  .Call("sapply_oppC", myls) }) 
#   user  system elapsed 
#  0.048   0.000   0.047 

#evaluate an R closure iteratively
system.time({ sapplyRC =  .Call("sapply_oppR", myls, "oppR") }) 
#   user  system elapsed 
#  3.348   0.000   3.358 

#evaluate an R builtin iteratively
system.time({ sapplyRCprim =  .Call("sapply_oppR", myls, "-") }) 
#   user  system elapsed 
#  0.652   0.000   0.653 

#loop with a R closure
system.time({ forR = for_oppR(myls, "oppR") })
#   user  system elapsed 
#  4.396   0.000   4.409 

#loop with an R builtin
system.time({ forRprim = for_oppR(myls, "-") })
#   user  system elapsed 
#  1.908   0.000   1.913 

#for reference and testing 
system.time({ sapplyR = unlist(lapply(myls, oppR)) })
#   user  system elapsed 
#  7.080   0.068   7.170 
system.time({ sapplyRprim = unlist(lapply(myls, `-`)) }) 
#   user  system elapsed 
#  3.524   0.064   3.598 

all.equal(sapplyR, sapplyRprim)
#[1] TRUE 
all.equal(sapplyR, sapplyC)
#[1] TRUE
all.equal(sapplyR, sapplyRC)
#[1] TRUE
all.equal(sapplyR, sapplyRCprim)
#[1] TRUE
all.equal(sapplyR, forR)
#[1] TRUE
all.equal(sapplyR, forRprim)
#[1] TRUE

(平均の発見の元の例に従います):

#all computations in C
all_C = inline::cfunction(sig = c(R_ls = "list"), body = '
    SEXP tmp, ans;
    PROTECT(ans = allocVector(REALSXP, LENGTH(R_ls)));

    double *ptmp, *pans = REAL(ans);

    for(int i = 0; i < LENGTH(R_ls); i++) {
        pans[i] = 0.0;

        PROTECT(tmp = coerceVector(VECTOR_ELT(R_ls, i), REALSXP));
        ptmp = REAL(tmp);

        for(int j = 0; j < LENGTH(tmp); j++) pans[i] += ptmp[j];

        pans[i] /= LENGTH(tmp);

        UNPROTECT(1);
    }

    UNPROTECT(1);
    return(ans);
')

#a very simple `lapply(x, mean)`
C_and_R = inline::cfunction(sig = c(R_ls = "list"), body = '
    SEXP call, ans, ret;

    PROTECT(call = allocList(2));
    SET_TYPEOF(call, LANGSXP);
    SETCAR(call, install("mean"));

    PROTECT(ans = allocVector(VECSXP, LENGTH(R_ls)));
    PROTECT(ret = allocVector(REALSXP, LENGTH(ans)));

    for(int i = 0; i < LENGTH(R_ls); i++) {
        SETCADR(call, VECTOR_ELT(R_ls, i));
        SET_VECTOR_ELT(ans, i, eval(call, R_GlobalEnv));
    }

    double *pret = REAL(ret);
    for(int i = 0; i < LENGTH(ans); i++) pret[i] = REAL(VECTOR_ELT(ans, i))[0];

    UNPROTECT(3);
    return(ret);
')                    

R_lapply = function(x) unlist(lapply(x, mean))                       

R_loop = function(x) 
{
    ans = numeric(length(x))
    for(i in seq_along(x)) ans[i] = mean(x[[i]])
    return(ans)
} 

R_loopcmp = compiler::cmpfun(R_loop)


set.seed(007); myls = replicate(1e4, runif(1e3), simplify = FALSE)
all.equal(all_C(myls), C_and_R(myls))
#[1] TRUE
all.equal(all_C(myls), R_lapply(myls))
#[1] TRUE
all.equal(all_C(myls), R_loop(myls))
#[1] TRUE
all.equal(all_C(myls), R_loopcmp(myls))
#[1] TRUE

microbenchmark::microbenchmark(all_C(myls), 
                               C_and_R(myls), 
                               R_lapply(myls), 
                               R_loop(myls), 
                               R_loopcmp(myls), 
                               times = 15)
#Unit: milliseconds
#            expr       min        lq    median        uq      max neval
#     all_C(myls)  37.29183  38.19107  38.69359  39.58083  41.3861    15
#   C_and_R(myls) 117.21457 123.22044 124.58148 130.85513 169.6822    15
#  R_lapply(myls)  98.48009 103.80717 106.55519 109.54890 116.3150    15
#    R_loop(myls) 122.40367 130.85061 132.61378 138.53664 178.5128    15
# R_loopcmp(myls) 105.63228 111.38340 112.16781 115.68909 128.1976    15

10
data.frameをマトリックスに変換するコストについての素晴らしい点と、ベンチマークを提供してくれてありがとう。
Joshua Ulrich

all_CとのC_and_R関数をコンパイルすることはできませんでしたが、それは非常に良い答えです。私ものドキュメンテーションで見つけlapplyの古いRバージョンの実際のRの含まれているループを、私はバーンズに言及していたと思われるし始めていること、それ以来ベクトル化された古いバージョン、これは私の質問に、実際の答えです。.. ..compiler::cmpfunfor
デビッドアレンブルク2015年

@DavidArenburg:la1からのベンチマーク?compiler::cmpfunは、all_C機能を除いてすべて同じ効率をもたらすようです。確かに、それは確かに定義の問題になるでしょう。「ベクトル化」とは、スカラーだけでなく、Cコードを含む関数、Cでのみ計算を使用する関数を受け入れる関数を意味しますか?
alexis_laz 2015年

1
私は推測するすべてという理由だけで、Rの関数は、それらの中にCコードを持っているすべての Rでは、(いくつかの言語で書かなければならなかった)の関数です。つまり、基本的に、私がそれを正しく理解していれば、Cコードの反復でlapplyR関数を評価しているからといって、それがベクトル化されていないということですか。
David Arenburg、2015年

5
@DavidArenburg:なんらかの方法で「ベクトル化」を定義する必要がある場合、私はおそらく言語学的アプローチを選択するでしょう。すなわち、「ベクトル」を受け入れ、処理する方法を知っている関数です。高速か、低速か、Cで書かれているか、Rで書かれているか、その他何でもかまいません。Rでは、ベクトル化の重要性は、多くの関数がCで記述され、ベクトルを処理することですが、他の言語では、ユーザーは通常、入力をループして平均を求めます。これにより、ベクトル化は、速度、効率、安全性、および堅牢性と間接的に関連付けられます。
alexis_laz 2015年

65

私にとって、ベクトル化とは主に、コードの記述と理解を容易にすることです。

ベクトル化された関数の目的は、forループに関連する簿記を排除することです。たとえば、次の代わりに:

means <- numeric(length(mtcars))
for (i in seq_along(mtcars)) {
  means[i] <- mean(mtcars[[i]])
}
sds <- numeric(length(mtcars))
for (i in seq_along(mtcars)) {
  sds[i] <- sd(mtcars[[i]])
}

あなたは書ける:

means <- vapply(mtcars, mean, numeric(1))
sds   <- vapply(mtcars, sd, numeric(1))

これにより、同じもの(入力データ)と違うもの(適用する関数)を簡単に確認できます。

ベクトル化の2番目の利点は、forループがRではなくCで記述されることが多いことです。これにより、パフォーマンスが大幅に向上しますが、ベクトル化の主要な特性だとは思いません。ベクトル化は、基本的にはコンピューターの作業を保存するのではなく、脳を保存することです。


5
C forループとR ループの間に意味のあるパフォーマンスの違いはないと思います。Cループはコンパイラーによって最適化される可能性がありますが、パフォーマンスの主なポイントは、ループの内容が効率的かどうかです。そして、明らかにコンパイルされたコードは通常、解釈されたコードよりも高速です。しかし、おそらくそれがあなたが言うつもりだったのです。
Roland

3
@Rolandええ、それ自体はforループ自体ではなく、それを取り巻くすべてのものです(関数呼び出しのコスト、インプレースで変更する機能など)。
ハドリー2015年

10
@DavidArenburg "無用な一貫性は小さな心のホブゴブリンです";)
ハドリー

6
いいえ、コードをベクトル化する主なポイントはパフォーマンスではないと思います。ループをラッププライに書き換えることは、それが速くなくても有益です。dplyrの主なポイントは、データ操作を簡単に表現できることです(そして、高速であることも非常に優れています)。
ハドリー2015年

12
@DavidArenburgは、経験豊富なRユーザーだからです。ほとんどの新しいユーザーは、ループをはるかに自然に感じるため、ベクトル化するように奨励する必要があります。私にとって、colMeansのような関数を使用することは必ずしもベクトル化に関するものではなく、誰かが既に書いた高速コードの再利用に関するものです
ハドリー

49

Patrick Burnsの見解では、コードのベクトル化ではなく、ループの非表示であることに同意します。理由は次のとおりです。

次のCコードスニペットを検討してください。

for (int i=0; i<n; i++)
  c[i] = a[i] + b[i]

たちがをしたいかは非常に明確です。しかし、タスクがどのように実行されるか、またはどのように実行できるかは、実際にはそうではありません。forループデフォルトでは、シリアル構造です。並行して実行できるかどうか、またはどのように実行できるかは通知されません。

最も明白な方法は、コードが順次実行されることです。レジスタにロードa[i]b[i]てから、それらを追加し、結果をに格納し、c[i]それぞれに対してこれを行いiます。

ただし、最近のプロセッサには、同じ操作を実行するときに同じ命令(たとえば、上記の2つのベクトルを追加する)にデータのベクトルを操作できるベクトルまたはSIMD命令セットがあります。プロセッサ/アーキテクチャに応じて、追加言う、4つの数字をからすることは可能かもしれないし、一度に同じ命令の下で、代わりの一つ。ab

たとえば、単一命令の複数データを活用して、データレベルの並列処理を実行します。たとえば、一度に4つのものを読み込み、4つずつ追加し、一度に4つ保存します。そして、これはコードのベクトル化です。

これは、複数の計算が同時に実行されるコードの並列化とは異なります。

コンパイラーがこのようなコードのブロックを識別し、それらを自動的にベクトル化することは素晴らしいことですが、これは難しい作業です。自動コードベクトル化は、コンピューターサイエンスの挑戦的な研究トピックです。しかし、時間の経過とともに、コンパイラーはより良くなってきました。ここで自動ベクトル化機能を確認できGNU-gcc ます。同様にLLVM-clang ここに。またgcc、and ICC(Intel C ++コンパイラ)と比較して、最後のリンクでいくつかのベンチマークを見つけることができます。

gcc(私はオンですv4.9)たとえば、-O2レベルの最適化でコードを自動的にベクトル化しません。したがって、上記のコードを実行すると、順次実行されます。これが長さ5億の2つの整数ベクトルを追加するタイミングです。

フラグを追加するか、-ftree-vectorize最適化をlevelに変更する必要があります-O3。(他の追加の最適化-O3実行することに注意してください)。このフラグ-fopt-info-vecは、ループが正常にベクトル化されたことを通知するので便利です)。

# compiling with -O2, -ftree-vectorize and  -fopt-info-vec
# test.c:32:5: note: loop vectorized
# test.c:32:5: note: loop versioned for vectorization because of possible aliasing
# test.c:32:5: note: loop peeled for vectorization to enhance alignment    

これは、関数がベクトル化されていることを示しています。長さが5億の整数ベクトルでベクトル化されていないバージョンとベクトル化されたバージョンの両方を比較するタイミングは次のとおりです。

x = sample(100L, 500e6L, TRUE)
y = sample(100L, 500e6L, TRUE)
z = vector("integer", 500e6L) # result vector

# non-vectorised, -O2
system.time(.Call("Csum", x, y, z))
#    user  system elapsed 
#   1.830   0.009   1.852

# vectorised using flags shown above at -O2
system.time(.Call("Csum", x, y, z))
#    user  system elapsed 
#   0.361   0.001   0.362

# both results are checked for identicalness, returns TRUE

この部分は、連続性を失うことなく安全にスキップできます。

コンパイラーは常にベクトル化するのに十分な情報を持っているとは限りません。並列プログラミング用のOpenMP仕様を使用することもできます。これは、コンパイラーにコードをベクトル化するように指示するsimdコンパイラーディレクティブも提供します。コードを手動でベクトル化するときは、メモリのオーバーラップや競合状態などがないことを確認することが不可欠です。そうしないと、結果が不正確になります。

#pragma omp simd
for (i=0; i<n; i++) 
  c[i] = a[i] + b[i]

これを行うことにより、特にコンパイラに、何があってもベクトル化するように依頼します。コンパイル時フラグを使用して、OpenMP拡張機能をアクティブにする必要があります-fopenmp。それを行うことによって:

# timing with -O2 + OpenMP with simd
x = sample(100L, 500e6L, TRUE)
y = sample(100L, 500e6L, TRUE)
z = vector("integer", 500e6L) # result vector
system.time(.Call("Cvecsum", x, y, z))
#    user  system elapsed 
#   0.360   0.001   0.360

すごい!これはgcc v6.2.0とllvm clang v3.9.0(どちらもhomebrew、MacOS 10.12.3を介してインストール)でテストされ、どちらもOpenMP 4.0をサポートしています。


この意味で、配列プログラミングに関するWikipediaのページでは、配列全体を操作する言語は通常、ベクトル化された操作と呼ばれていると述べていますが、実際にはベクトル化されていない限り、IMO はループを隠しています。

Rの場合には、偶数rowSums()又はcolSums()コードCには利用していないコードベクトル化 IIUCと、これはCのループにすぎませんlapply()。の場合apply()、それはRにあります。したがって、これらはすべてループ非表示です。

つまり、R関数を次のようにラップします。

ちょうど書いforループCあなたのコードをベクトル化=!。
ちょうど書いforループRあなたのコードをベクトル化=!。

たとえば、Intel Math Kernel Library(MKL)は、ベクトル化された形式の関数を実装します。

HTH


参照:

  1. ジェームズレインダース、インテルによる講演(この回答は、主にこの優れた講演を要約する試みです)

35

すばらしい答え/コメントをいくつかの一般的な答えにまとめ、いくつかの背景を提供するには:Rには4種類のループがあります(ベクトル化されていない順序からベクトル化された順序へ

  1. for各反復でR関数を繰り返し呼び出すR ループ(ベクトル化されていません
  2. 各反復でR関数を繰り返し呼び出すCループ(ベクトル化されていません
  3. R関数を1回だけ呼び出すCループ(ややベクトル化
  4. R関数をまったく呼び出さ、独自のコンパイル済み関数を使用するプレーンなCループ(ベクトル化

だから、*apply家族は第二のタイプです。applyどちらが最初のタイプであるかを除いて

あなたはそのソースコードのコメントからこれを理解することができます

/ * .Internal(lapply(X、FUN))* /

/ *これは特別な.Internalなので、未評価の引数があります。これは
クロージャーラッパーから呼び出されるため、XとFUNはお約束です。FUNはbquoteなどで使用するために評価されていない必要があります。* /

つまり、lapplyCコードはRから未評価の関数を受け入れ、後でCコード自体の中で評価します。これは基本的にlapplys .Internal呼び出しの違いです

.Internal(lapply(X, FUN))

FUNRの機能を保持している引数を

そして、colMeans .Internal呼び出していません持っているFUN引数に

.Internal(colMeans(Re(x), n, prod(dn), na.rm))

colMeansは、使用する必要のある関数を正確にlapply把握しているのとは異なり、Cコード内で内部的に平均を計算します。

Cコード内の各反復におけるR関数の評価プロセスを明確に見ることができlapplyます

 for(R_xlen_t i = 0; i < n; i++) {
      if (realIndx) REAL(ind)[0] = (double)(i + 1);
      else INTEGER(ind)[0] = (int)(i + 1);
      tmp = eval(R_fcall, rho);   // <----------------------------- here it is
      if (MAYBE_REFERENCED(tmp)) tmp = lazy_duplicate(tmp);
      SET_VECTOR_ELT(ans, i, tmp);
   }

物事をまとめると、lapplyベクトル化されていない、それはプレーンなRの上の2つの可能な利点があるものの、forループを

  1. ループでのアクセスと割り当ては、Cの方が速いようです(つまりlapply、関数を使用する場合)。違いは大きいように見えますが、それでもマイクロ秒レベルのままであり、コストのかかることは、各反復でのR関数の評価です。簡単な例:

    ffR = function(x)  {
        ans = vector("list", length(x))
        for(i in seq_along(x)) ans[[i]] = x[[i]]
        ans 
    }
    
    ffC = inline::cfunction(sig = c(R_x = "data.frame"), body = '
        SEXP ans;
        PROTECT(ans = allocVector(VECSXP, LENGTH(R_x)));
        for(int i = 0; i < LENGTH(R_x); i++) 
               SET_VECTOR_ELT(ans, i, VECTOR_ELT(R_x, i));
        UNPROTECT(1);
        return(ans); 
    ')
    
    set.seed(007) 
    myls = replicate(1e3, runif(1e3), simplify = FALSE)     
    mydf = as.data.frame(myls)
    
    all.equal(ffR(myls), ffC(myls))
    #[1] TRUE 
    all.equal(ffR(mydf), ffC(mydf))
    #[1] TRUE
    
    microbenchmark::microbenchmark(ffR(myls), ffC(myls), 
                                   ffR(mydf), ffC(mydf),
                                   times = 30)
    #Unit: microseconds
    #      expr       min        lq    median        uq       max neval
    # ffR(myls)  3933.764  3975.076  4073.540  5121.045 32956.580    30
    # ffC(myls)    12.553    12.934    16.695    18.210    19.481    30
    # ffR(mydf) 14799.340 15095.677 15661.889 16129.689 18439.908    30
    # ffC(mydf)    12.599    13.068    15.835    18.402    20.509    30
  2. @Rolandで述べたように、解釈されたRループではなく、コンパイルされたCループを実行します


ただし、コードをベクトル化する場合は、いくつかの点を考慮する必要があります。

  1. あなたのデータセット(レッツ・コール、それは場合df)クラスであるdata.frame、いくつかのベクトル化機能は、(のようなcolMeanscolSumsrowSumsこれは彼らが設計された方法であるという理由だけで、など)、第1の行列に変換する必要があります。これは、大きなものの場合、dfこれが大きなオーバーヘッドを生み出す可能性があることを意味します。一方でlapply、それは外の実際のベクトルを抽出して、これを行う必要はありませんdf(とdata.frameあなたは多くの列が、多くの行ないを持っている場合は、このようにベクトルのリストだけである)と、lapply(df, mean)時々 、より良い選択肢であることができますcolMeans(df)
  2. もう1つ覚えておかなければならないのは、Rには、などのさまざまな関数型があり、.Primitiveジェネリック(S3S4)については、追加情報についてはこちらを参照てください。ジェネリック関数は、メソッドディスパッチを実行する必要があります。たとえば、meanはジェネリックS3関数で、sumPrimitiveです。したがって、上記の理由から、いくつかの時間lapply(df, sum)は非常に効率的である可能性がありますcolSums

1
非常にまとまりのある要約。ほんのいくつかのメモ:(1)Cは「data.frame」を処理する方法を知っています。これは、それらが属性を持つ「リスト」であるためです。colMeans行列のみを処理するように構築されているのは、それなどです。(2)私はあなたの3番目のカテゴリーに少し混乱しています。あなたが何を言っているのかわかりません。(3)特にを参照しているので、RとCのlapply"[<-"に違いはないと思います。SET_VECTOR_ELT私はあなたのポイントを逃していない限り、どちらも「リスト」(SEXP)を事前に割り当て、それを各反復(C)に記入します。
alexis_laz 2015年

2
do.callC環境で関数呼び出しを作成し、それを評価するだけだという点について、私はあなたの要点を理解します。ループやベクトル化とは違うことをするので、比較するのに苦労しています。高価な反復R関数呼び出し(比較ですので、あなたは、右の非常rerultにアクセスし、CとRの違いを割り当て、両方の滞在がマイクロ秒レベルで、結果には影響しませんについて、実際には、しているR_loopR_lapply私の答えでは、 )。(私はあなたの投稿をベンチマークで編集します。私はあなたが、まだ、気にしないことを願っています)
alexis_laz

2
私は反対していません---そして、私は正直に言って、あなたが反対していることについて混乱しています。私の以前のコメントはもっといい言葉で書かれたかもしれない。「ベクトル化」という用語には、しばしば混同される2つの定義があるため、使用されている用語を絞り込んでいます。これは議論の余地がないと思います。Burnsと実装の意味でのみ使用したいようですが、Hadleyと多くのR-Coreメンバー(Vectorize()例として)はUIの意味でも使用しています。このスレッドでの意見の相違の多くは、2つの別々ではあるが関連する概念に1つの用語を使用したことが原因であると思います。
グレゴールトーマス

3
@DavidArenburgとは、RまたはCの下にforループがあるかどうかに関係なく、UIの意味でのベクトル化ではありませんか?
グレゴールトーマス

2
@ DavidArenburg、Gregor、私は混乱が「コードのベクトル化」と「ベクトル化された関数」の間であると思います。Rでは、使用法は後者に向かっているようです。「コードのベクトル化」は、同じ命令で長さ「k」のベクトルを操作することを示しています。fnをラップします。ループ状のコードの周りは「ベクトル化された関数」になり(そうです、意味がなく、混乱します。同意しますが、ループの非表示またはベクトルi / p関数の方がいいでしょう)、コードのベクトル化とは何の関係もありません。Rでは、applyはベクトル化された関数ですが、コードをベクトル化するのではなく、ベクトルを操作します。
アルン2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.