2014-06-09 16 views
6

Ho appena finito di scrivere una nuova versione del pacchetto ABCoptim usando Rcpp. Con circa 30x di accelerazioni, sono molto contento delle prestazioni della nuova versione (rispetto alla vecchia versione), ma mi sto ancora preoccupando di avere spazio per migliorare le prestazioni senza modificare troppo il codice.All'interno delle funzioni C++, in che modo gli oggetti Rcpp vengono passati ad altre funzioni (per riferimento o per copia)?

All'interno della funzione principale di ABCoptim (scritto in C++) sto passando attorno a un oggetto Rcpp :: List contenente "posizioni di api" (NumericMatrix) e alcuni NumericVectors con informazioni importanti per l'algoritmo stesso. La mia domanda è, quando sto passando un oggetto Rcpp :: List attorno ad altre funzioni, ad es.

#include <Rcpp.h> 

using namespace Rcpp; 

List ABCinit([some input]){[some code here]}; 
void ABCfun2(List x){[some code here]}; 
void ABCfun3(List x){[some code here]}; 

List ABCmain([some input]) 
{ 
    List x = ABCinit([some input]); 
    while ([some statement]) 
    { 
    ABCfun2(x); 
    ABCfun3(x); 
    } 
    ... 

    return List::create(x["results"]); 
} 

Cosa fa Rcpp nel ciclo while? L'oggetto x viene passato per riferimento o per copia profonda alle funzioni ABCfun2 e ABCfun3? Ho visto l'uso di 'const list & x', che mi dice che posso passare oggetti Rcpp usando i puntatori, ma il fatto è che ho bisogno che questa lista sia variabile (e non costante), c'è comunque da migliorare ? Temo che la copia iterativa di questo elenco x possa rallentare il mio codice.

PS: Sono ancora nuovo in C++, inoltre sto usando Rcpp per imparare il C++.

risposta

11

non c'è profonda copia in Rcpp a meno che non lo richieda con clone. Quando passi di valore, stai creando un nuovo oggetto t utilizza lo stesso oggetto R sottostante.

Quindi il diverso è piccolo tra passaggio per valore e passaggio per riferimento.

Tuttavia, quando si passa per valore, è necessario pagare il prezzo per proteggere l'oggetto sottostante ancora una volta. Potrebbe incorrere in un costo aggiuntivo in quanto Rcpp si basa sul ricorsivo non molto efficiente R_PreserveObject.

La mia linea guida dovrebbe passare per riferimento ogniqualvolta possibile, in modo da non pagare un prezzo di protezione extra. Se sai che ABCfun2 non cambierà l'oggetto, ti consiglio di passare per riferimento a const: ABCfun2(const List&). Se hai intenzione di apportare modifiche allo List, allora ti consigliamo di utilizzare ABCfun2(List&).

Considerate questo codice:

#include <Rcpp.h> 
using namespace Rcpp ; 

#define DBG(MSG,X) Rprintf("%20s SEXP=<%p>. List=%p\n", MSG, (SEXP)X, &X) ; 

void fun_copy(List x, const char* idx){ 
    x[idx] = "foo" ; 
    DBG("in fun_copy: ", x) ; 

} 
void fun_ref(List& x, const char* idx){ 
    x[idx] = "bar" ; 
    DBG("in fun_ref: ", x) ; 
} 


// [[Rcpp::export]] 
void test_copy(){ 

    // create a list of 3 components 
    List data = List::create(_["a"] = 1, _["b"] = 2) ; 
    DBG("initial: ", data) ; 

    fun_copy(data, "a") ; 
    DBG("\nafter fun_copy (1): ", data) ; 

    // alter the 1st component of ths list, passed by value 
    fun_copy(data, "d") ; 
    DBG("\nafter fun_copy (2): ", data) ; 


} 

// [[Rcpp::export]] 
void test_ref(){ 

    // create a list of 3 components 
    List data = List::create(_["a"] = 1, _["b"] = 2) ; 
    DBG("initial: ", data) ; 

    fun_ref(data, "a") ; 
    DBG("\nafter fun_ref (1): ", data) ; 

    // alter the 1st component of ths list, passed by value 
    fun_ref(data, "d") ; 
    DBG("\nafter fun_ref (2): ", data) ; 


} 

Tutto quello che sto facendo è passare una lista a una funzione, aggiornare e stampare alcune informazioni circa la puntatore all'oggetto R sottostante e il puntatore all'oggetto List (this).

ecco i risultati di ciò che accade quando chiamo test_copy e test_ref:

> test_copy() 
      initial: SEXP=<0x7ff97c26c278>. List=0x7fff5b909fd0 
     in fun_copy: SEXP=<0x7ff97c26c278>. List=0x7fff5b909f30 

after fun_copy (1): SEXP=<0x7ff97c26c278>. List=0x7fff5b909fd0 
$a 
[1] "foo" 

$b 
[1] 2 

     in fun_copy: SEXP=<0x7ff97b2b3ed8>. List=0x7fff5b909f20 

after fun_copy (2): SEXP=<0x7ff97c26c278>. List=0x7fff5b909fd0 
$a 
[1] "foo" 

$b 
[1] 2 

Si comincia con un elenco esistente associata a un oggetto R.

  initial: SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0 

passiamo per valore di fun_copy modo da ottenere un nuovo List ma utilizzando lo stesso oggetto R sottostante:

 in fun_copy: SEXP=<0x7fda4926d278>. List=0x7fff5bb5ef30 

Abbiamo uscita fun_copy. stesso oggetto R sottostante di nuovo, e di nuovo al nostro originale List:

after fun_copy (1): SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0 

Ora noi chiamiamo di nuovo fun_copy ma questa volta l'aggiornamento di un componente che non era sulla lista: x["d"]="foo".

 in fun_copy: SEXP=<0x7fda48989120>. List=0x7fff5bb5ef20 

List aveva altra scelta che crearsi un nuovo oggetto R sottostante, ma questo oggetto è Sottostante solo al locale List. Pertanto, quando usciamo da get_copy, torniamo al nostro originale List con il sottostante originale SEXP.

after fun_copy (2): SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0 

La cosa fondamentale è che la prima volta "a" era già nella lista, quindi abbiamo aggiornate direttamente i dati. Poiché l'oggetto locale a fun_copy e l'oggetto esterno da test_copy condividono lo stesso oggetto R sottostante, le modifiche all'interno di fun_copy sono state propagate.

La seconda volta, fun_copy cresce l'oggetto locale List, associandolo a un nuovo SEXP che non si propaga alla funzione esterna.

Ora consideriamo cosa succede quando si passa da riferimento:

> test_ref() 
      initial: SEXP=<0x7ff97c0e0f80>. List=0x7fff5b909fd0 
     in fun_ref: SEXP=<0x7ff97c0e0f80>. List=0x7fff5b909fd0 

    after fun_ref(1): SEXP=<0x7ff97c0e0f80>. List=0x7fff5b909fd0 
$a 
[1] "bar" 

$b 
[1] 2 

     in fun_ref: SEXP=<0x7ff97b5254c8>. List=0x7fff5b909fd0 

    after fun_ref(2): SEXP=<0x7ff97b5254c8>. List=0x7fff5b909fd0 
$a 
[1] "bar" 

$b 
[1] 2 

$d 
[1] "bar" 

C'è solo un oggetto List0x7fff5b909fd0. Quando dobbiamo ottenere un nuovo SEXP nella seconda chiamata, viene propagato correttamente al livello esterno.

Per me, il comportamento che si ottiene quando si passa da riferimenti è molto più facile da ragionare.

+0

Questo è molto chiaro per me ora! Grazie Romain e Dirk! Entrambi i tuoi consigli sono stati molto utili. Grande fan di Rcpp che cerca di mostrarlo al mondo di lingua spagnola http://rstudio-pubs-static.s3.amazonaws.com/16153_7d5a52078aa24b3da140e3f94e48226b.html#/ =) Grazie! – gvegayon

+1

Buon post. Potresti aggiungere alcune misurazioni su un numero (abbastanza grande) di chiamate per vedere qual è il costo effettivo? –

10

Brevemente:

  1. void ABCfun(List x) passa dal valore ma poi List è un oggetto Rcpp avvolgendo un SEXP che è un puntatore - quindi il costo qui è inferiore a quello che un programmatore C++ sarebbe sospetto ed è in effetti leggero. (Ma, come Romain osserva giustamente, v'è il costo in uno strato di protezione supplementare.)

  2. void ABCfun(const List x) promesse di non cambiare x, ma ancora una volta perché è un puntatore ...

  3. void ABCfun(const List & x) sembra più normale un programmatore C++ ed è supportato in Rcpp dall'anno scorso.

Ipso facto, nel contesto Rcpp tutti e tre sono pressappoco uguali. Ma dovresti pensare secondo le migliori pratiche del C++ e preferire 3. Come un giorno puoi usare uno std::list<....> invece in questo caso il riferimento const è preferibile (Scott Meyers ha un intero post su questo in Effective C++ (o forse nel compagno di più efficace C++).

ma la lezione più importante è che non si dovrebbe credere a quello che la gente vi dice su internet, ma piuttosto misurare e profilo per quanto possibile.

Problemi correlati