Защо тази опростена версия на функцията cpp е по-бавна?

Помислете за това сравнение:

require(Rcpp)
require(microbenchmark)

cppFunction('int cppFun (int x) {return x;}')
RFun = function(x) x

x=as.integer(2)
microbenchmark(RFun=RFun(x),cppFun=cppFun(x),times=1e5)

Unit: nanoseconds
   expr  min   lq      mean median   uq      max neval cld
   RFun  302  357  470.2047    449  513   110152 1e+06  a 
 cppFun 2330 2598 4045.0059   2729 2879 68752603 1e+06   b

cppFun изглежда по-бавен от RFun. Защо е така? Различават ли се времената за извикване на функциите? Или самата функция се различава по време на работа? Време ли е за предаване и връщане на аргументи? Има ли преобразуване на данни или копиране на данни, за които не знам, когато данните се предават на (или се връщат от) cppFun?


person Remi.b    schedule 28.09.2016    source източник
comment
Интерфейсът на чуждата функция има допълнителни разходи, което не би трябвало да е изненада. Когато вашата функция започне да върши някаква наистина полезна работа, вероятно никога няма да забележите режийните разходи. Но точно сега вашата функция не струва нищо, освен режийните разходи.   -  person Ben Voigt    schedule 28.09.2016
comment
Представете си, че сте R и CPP е вашият трудолюбив колега. Какво е по-бързо: да повторите незабавно всяко изречение, което ви е казано или да отидете при колегата си и да го помолите да го направи вместо вас? Сега сменете повтарянето на изречение с даване на данъчни декларации и нещата може да се окажат различни. (Отказ от отговорност: Никога не съм се опитвал да правя данъчни декларации в C++. Може да е по-бързо, но вероятно е и по-сложно, отколкото да го правя в R.)   -  person Jeroen Mostert    schedule 29.09.2016
comment
@JeroenMostert Като докторант, моите данъчни декларации са доста лесни за работа ;) Благодаря за метафората!   -  person Remi.b    schedule 29.09.2016
comment
Благодаря! Не знам много за тези „функционални интерфейси“ и никога не съм чувал за понятието „режийни“ за извикване на функция. Виждам, че когато въведа cppFun, се изписва .Primitive(".Call")(<pointer: 0x1153d4cd0>, x). Защо извикването на такава функция отнема повече време?   -  person Remi.b    schedule 29.09.2016
comment
@Remi.b: Очаквам с нетърпение вашето по-бързо внедряване. След като направите това, имате ли нещо против да работите върху машина на времето и perpertuum mobile?   -  person Dirk Eddelbuettel    schedule 29.09.2016
comment
@DirkEddelbuettel Хм... Не разбирам причината за този сарказъм. Искате да кажете, че моето проследяване в коментарите е твърде широко? (Вече използвам Rcpp за много по-сложни процеси, просто се чудех дали извикването на cpp функция много пъти ще има цена).   -  person Remi.b    schedule 29.09.2016


Отговори (1)


Това просто не е добре поставен или обмислен въпрос, както показват коментарите по-горе.

Предполагаемата базова линия на празна функция просто не е една. Всяка функция, създадена чрез cppFunction() и др., ще извика една R функция, свързваща се с някоя C++ функция. Така че това просто не може да бъде равно.

Ето едно малко по-смислено сравнение. Като за начало, нека направим функцията R пълна с curlies. Второ, нека извикаме друга (вътрешна) функция на компилатора:

require(Rcpp)
require(microbenchmark)

cppFunction('int cppFun (int x) {return x;}')
RFun1 <- function(x) { x }
RFun2 <- function(x) { .Primitive("abs")(x) }

print(microbenchmark(RFun1(2L), RFun2(2L), cppFun(2L), times=1e5))

В моята кутия виждам а) по-голяма разлика между версии 1 и 2 (или функцията C++) и б) малко наказание над вътрешната функция. Но извикването на ВСЯКА компилирана функция от R струва.

Unit: nanoseconds
       expr min   lq     mean median   uq     max neval
  RFun1(2L) 171  338  434.136    355  408 2659984 1e+05
  RFun2(2L) 683  937 1334.046   1257 1341 7605628 1e+05
 cppFun(2L) 721 1131 1416.091   1239 1385 8544656 1e+05

Както казваме в реалния свят: няма безплатен обяд.

person Dirk Eddelbuettel    schedule 28.09.2016
comment
О, това има пълен смисъл. Това ли е режийното, което @BenVoigt има предвид в коментарите? Благодаря - person Remi.b; 29.09.2016
comment
Да, между другото. cppFun() и RFun2() всъщност имат тяло с код за изпълнение. RFun1() не го прави. Как е възможно те да бъдат равни по време на изпълнение? Вие също правите грешка при профилирането на новобранец, като се опитвате да измервате празно. Това наистина няма много смисъл, както е представено. - person Dirk Eddelbuettel; 29.09.2016
comment
Добре, мисля, че има смисъл за мен +1. Благодаря много! - person Remi.b; 29.09.2016