2016-06-15 6 views
1

Rcppを使用して、大規模な医療画像ファイルのグループに任意のRコードを適用するパッケージを作成しています。自分のRcppの実装が元の純粋なCバージョンよりもかなり遅いことに気付きました。 Functionを介して関数を呼び出すこととの違いを、元のRf_evalと比較して追跡しました。私の質問は、4倍のパフォーマンス低下に近づくのはなぜですか、そしてRf_evalに近いパフォーマンスを得るために関数呼び出しを高速化する方法がありますか?Rcpp関数がRf_evalより遅い

例:それは物事がプログラマにばかばかしいほどクリーンが見えますので

library(Rcpp)                                       
library(inline)                                       
library(microbenchmark)                                     

cpp_fun1 <-                                        
    '                                          
Rcpp::List lots_of_calls(Function fun, NumericVector vec){                            
    Rcpp::List output(1000);                                    
    for(int i = 0; i < 1000; ++i){                                  
    output[i] = fun(NumericVector(vec));                                
    }                                          
    return output;                                      
}                                          
'                                          

cpp_fun2 <-                                        
    '                                          
Rcpp::List lots_of_calls2(SEXP fun, SEXP env){                               
    Rcpp::List output(1000);                                    
    for(int i = 0; i < 1000; ++i){                                  
    output[i] = Rf_eval(fun, env);                                  
    }                                          
    return output;                                      
}                                          
'                                          

lots_of_calls <- cppFunction(cpp_fun1)                                 
lots_of_calls2 <- cppFunction(cpp_fun2)                                 

microbenchmark(lots_of_calls(mean, 1:1000),                                
       lots_of_calls2(quote(mean(1:1000)), .GlobalEnv)) 

結果

Unit: milliseconds 
              expr  min  lq  mean median  uq  max neval 
        lots_of_calls(mean, 1:1000) 38.23032 38.80177 40.84901 39.29197 41.62786 54.07380 100 
lots_of_calls2(quote(mean(1:1000)), .GlobalEnv) 10.53133 10.71938 11.08735 10.83436 11.03759 18.08466 100 
+1

はあなたがCからR関数を呼び出す_areことを知っ++ _すべて見合っオーバーヘッドを含むていますか?これがちょうどRより速いとはどういうことが期待できますか? –

+1

また、 'Rf_eval()'があなたのニーズに合っていると思うなら、それを使わないのはなぜですか?例で示すように、Rcppはあなたがそうするのを妨げません。 –

+2

また、C++コンテキストから直接 'Rf_eval()'を呼び出すことは、Rエラー(C 'longjmp's)がC++オブジェクトのデストラクタをバイパスし、メモリがリークする/一般的に未定義の動作を引き起こすので危険です。 'Rcpp :: Function'はそれが起こらないことを確認しようとします。 –

答えて

4

Rcppは素晴らしいです。清潔さには、テンプレート化された応答と実行時間の重くなる一連の前提の形のコストがあります。しかし、一般化されたコードと特定のコードのセットアップの場合がそうである。

たとえば、Rcpp::Functionのコールルートをとります。初期構成とRf_revalの変更バージョンへの外部呼び出しでは、Rcpp_eval.hで指定された特別なRcppの特別な評価関数が必要です。この機能は、それに関連するShieldを介してRに電話をかけたときに機能エラーから保護するために保護されています。それで...

これに対して、Rf_evalはどちらもありません。失敗した場合は、パドルを持たないクリークの上にいます。 (そのためR_tryEval経由implement error catchingあなたは、もちろん、場合を除きます。)

これは言われていることで、計算をスピードアップするための最良の方法は、単純にC++に計算するために必要なすべてのものを書くことです。

+2

私は、プログラマに馬鹿げたように見えるものが好きです。私はその日を引用する必要があるかもしれません:) –

+0

@DirkEddelbuettelぜひ、ぜひ! – coatless

+0

ありがとう@Coatless、 エラーキャッチは実行時間が4倍になることは驚くようです。この質問にインスパイアされたこの関数は、ユーザーが任意のRコードをbigmemoryのようなファイルのグループに適用できるようにすることを目的としています。だから私が知っている限り、完全なC++翻訳は不可能です。だから、基本的な質問は、Rcppおよび/またはR-C APIを使って一般的な適用を実装する最も効果的な方法であると思われます。 –

3

@coatlessによって作られた点の他に、あなたはリンゴとリンゴを比較していません。 Rf_evalは、ベクトルを関数に渡しません。さらに重要なことは、関数の練習をquote()で行います。

要するに、少しばかげています。

以下は、糖機能mean()を使用したより完全な例です。

#include <Rcpp.h> 
using namespace Rcpp; 

// [[Rcpp::export]] 
List callFun(Function fun, NumericVector vec) { 
    List output(1000); 
    for(int i = 0; i < 1000; ++i){ 
    output[i] = fun(NumericVector(vec)); 
    } 
    return output; 
} 

// [[Rcpp::export]] 
List callRfEval(SEXP fun, SEXP env){ 
    List output(1000); 
    for(int i = 0; i < 1000; ++i){ 
    output[i] = Rf_eval(fun, env); 
    } 
    return output; 
} 

// [[Rcpp::export]] 
List callSugar(NumericVector vec) { 
    List output(1000); 
    for(int i = 0; i < 1000; ++i){ 
    double d = mean(vec); 
    output[i] = d; 
    } 
    return output; 
} 

/*** R 
library(microbenchmark) 
microbenchmark(callFun(mean, 1:1000), 
       callRfEval(quote(mean(1:1000)), .GlobalEnv), 
       callSugar(1:1000)) 
*/ 

あなただけsourceCpp()このことができます:あなたは `Rcpp :: Function`使用する場合

R> sourceCpp("/tmp/ch.cpp") 

R> library(microbenchmark) 

R> microbenchmark(callFun(mean, 1:1000), 
+    callRfEval(quote(mean(1:1000)), .GlobalEnv), 
+    callSugar(1:1000)) 
Unit: milliseconds 
             expr  min  lq  mean median  uq  max neval 
         callFun(mean, 1:1000) 14.87451 15.54385 18.57635 17.78990 18.29127 114.77153 100 
callRfEval(quote(mean(1:1000)), .GlobalEnv) 3.35954 3.57554 3.97380 3.75122 4.16450 6.29339 100 
          callSugar(1:1000) 1.50061 1.50827 1.62204 1.51518 1.76683 1.84513 100 
R> 
+0

ありがとうDirk、 別のコメントに記載されているように、砂糖は私のニーズに合っていません。ファイル全体に共通の適用を実装しようとしています。私は引用された式が関数のトリックをどのようにしているのか、関数と引数を渡す方法について理解しています。 Rf_evalは式を評価しないのですか? –

+0

ここでは、範囲と目的を異にすることに同意しましょう。 –

関連する問題