2016-06-15 4 views
1

Я работаю над пакетом, который использует Rcpp для применения произвольного R-кода в группе больших файлов медицинских изображений. Я заметил, что моя реализация Rcpp значительно медленнее, чем исходная чистая версия C. Я проследил разницу, вызвав функцию через функцию, по сравнению с исходным 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

Вы знаете, что вы _are вызывающую функцию R от C++ _ включая все накладные расходы, соразмерные при использовании 'Rcpp :: Function'? Как вы можете ожидать, что это будет быстрее, чем просто R? –

+1

Кроме того, если вы считаете, что 'Rf_eval()' соответствует вашим потребностям, почему бы вам не использовать его? Rcpp не мешает вам делать это, как демонстрирует пример. –

+2

Кроме того, вызов 'Rf_eval()' непосредственно из контекста C++ опасен, поскольку ошибки R (то есть C 'longjmp's) будут обходить деструкторы объектов C++ и утечки памяти/вызывать неопределенное поведение в целом. 'Rcpp :: Function' пытается убедиться, что этого не происходит. –

ответ

4

Rcpp велик, потому что он делает вещи выглядят абсурдно чистый для программиста. Чистота имеет стоимость в виде шаблонных ответов и набор допущений, которые уменьшают время выполнения. Но так обстоит дело с обобщенной и специальной настройкой кода.

Возьмем, к примеру, маршрут вызова для Rcpp::Function. Первоначальная конструкция, а затем внешний вызов для модифицированной версии Rf_reval требует специальной специальной функции eval, заданной в Rcpp_eval.h. В свою очередь, эта функция обернута защитой для защиты от ошибки функции при вызове в R через связанный с ней связанный с ним Shield. И так далее ...

Для сравнения, Rf_eval не имеет ни того, ни другого. Если он терпит неудачу, вы будете за рулем без весла. (Если, конечно, вы implement error catching через R_tryEval для него.)

При этом не было сказано, лучший способ ускорить вычисления, чтобы просто написать все необходимое для вычисления в C++.

+2

Мне нравится _make [s] вещи выглядят абсурдно чистыми для программиста. Возможно, мне нужно будет процитировать это однажды :) –

+0

@DirkEddelbuettel непременно, вперед! – coatless

+0

Thanks @Coatless, Мне кажется удивительным, что обнаружение ошибок увеличило бы время выполнения в четыре раза. Эта функция, которая вдохновила этот вопрос, предназначена для того, чтобы позволить пользователю применять произвольный код R к группе файлов, что-то вроде bigmemory. Так что, насколько я знаю, полный перевод на C++ был бы невозможным. Таким образом, я полагаю, что основной вопрос заключается в том, что самый эффективный способ реализации общего применения с Rcpp и/или API R-C. –

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() это:

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

Thanks Dirk, Как уже упоминалось в другом комментарии, сахар не соответствует моим потребностям, я пытаюсь внедрить общий подход к файлам. Я также не совсем уверен, что понимаю, как цитируемое выражение играет трюки в функции и как оно обходит передачу функции и аргумента. Rf_eval не оценивает выражение? –

+0

Давайте просто соглашаемся не согласиться с прицелом и прицелом. –