2016-06-15 8 views
1

Ich habe an einem Paket gearbeitet, das Rcpp verwendet, um beliebigen R-Code über eine Gruppe von großen medizinischen Bilddateien anzuwenden. Ich habe festgestellt, dass meine Rcpp-Implementierung wesentlich langsamer ist als die ursprüngliche reine C-Version. Ich habe den Unterschied zum Aufruf einer Funktion über Function gegenüber dem ursprünglichen Rf_eval verfolgt. Meine Frage ist, warum gibt es eine fast vierfache Leistungseinbuße, und gibt es eine Möglichkeit, den Funktionsaufruf zu beschleunigen, um in der Leistung zu Rf_eval näher zu sein?Rcpp Funktion langsamer als Rf_eval

Beispiel:

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)) 

Ergebnisse

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

Sind Sie sich bewusst, dass Sie _sind eine eine R-Funktion von C++ Aufruf _ einschließlich aller angemessenen Aufwand, wenn Sie 'RCPP :: Function'? Wie können Sie erwarten, dass dies schneller als nur R ist? –

+1

Darüber hinaus, wenn Sie denken, 'Rf_eval()' ist, was Ihren Bedürfnissen entspricht, warum verwenden Sie es nicht? Rcpp verhindert das nicht, wie das Beispiel zeigt. –

+2

Das direkte Aufrufen von 'Rf_eval()' aus einem C++ - Kontext ist auch gefährlich, da R-Fehler (dh C 'longjmp's) die Destruktoren von C++ - Objekten umgehen und Speicherleck verursachen/undefiniertes Verhalten verursachen. 'Rcpp :: Function' versucht sicherzustellen, dass das nicht passiert. –

Antwort

4

Rcpp groß ist, weil es Dinge macht sauber den Programmierer unsinnig aussehen. Die Sauberkeit hat Kosten in Form von Vorlagenantworten und eine Reihe von Annahmen, die die Ausführungszeit belasten. Dies ist jedoch der Fall bei einem generalisierten vs. spezifischen Code-Setup.

Nehmen Sie zum Beispiel die Anrufroute für eine Rcpp::Function. Die anfängliche Konstruktion und dann der externe Aufruf einer modifizierten Version von Rf_reval erfordert eine spezielle Rcpp spezifische Eval-Funktion, die in Rcpp_eval.h angegeben ist. Diese Funktion ist wiederum in Schutzfunktionen eingekapselt, um vor einem Funktionsfehler zu schützen, wenn über eine ihr zugeordnete Shield in R gerufen wird. Und so weiter ...

Im Vergleich Rf_eval hat weder. Wenn es scheitert, bist du ohne Paddel am Bach. (Es sei denn natürlich, Sie implement error catching über R_tryEval für sie.)

Mit diesem wird gesagt, der beste Weg, um die Berechnung zu beschleunigen, ist einfach alles zu schreiben, die für die Berechnung in C++.

+2

Ich mag _make [s] Dinge sehen absurd sauber für den Programmierer_. Ich muss das vielleicht eines Tages zitieren :) –

+0

@DirkEddelbuettel auf alle Fälle, mach weiter! – coatless

+0

Dank @Coatless, Es scheint mir erstaunlich, dass Fehler fangen die Ausführungszeit vervierfachen würde. Diese Funktion, die diese Frage inspirierte, soll es dem Benutzer ermöglichen, beliebigen R-Code auf eine Gruppe von Dateien anzuwenden, etwa wie ein großer Speicher. Soweit ich weiß, wäre eine vollständige C++ - Übersetzung unmöglich. Ich nehme an, die zugrunde liegende Frage ist immer noch, was ist die performantesten Weg, um eine allgemeine Anwendung mit Rcpp und/oder der R-C-API zu implementieren –

3

Neben den Punkten von @coatless vergleichen Sie nicht einmal Äpfel mit Äpfeln. Ihr Rf_eval Beispiel übergibt den Vektor nicht an die Funktion und, noch wichtiger, spielt die Funktion über quote().

Kurz gesagt, es ist alles ein bisschen albern.

Unten finden Sie ein vollständigeres Beispiel mit der Zuckerfunktion 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)) 
*/ 

Sie können nur sourceCpp() dies:

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

Danke Dirk, Wie in einem anderen Kommentar erwähnt, Zucker passt nicht meine Bedürfnisse, ich versuche, eine generische Anwendung über Dateien zu implementieren. Ich bin mir auch nicht ganz sicher, ob ich verstehe, wie der zitierte Ausdruck der Funktion trickst und wie es darum geht, die Funktion und das Argument zu übergeben. Bewertet Rf_eval den Ausdruck nicht? –

+0

Lasst uns einfach zustimmen, über den Umfang und das Ziel zu streiten. –