2013-08-27 13 views
7

In R, sto usando una struttura interna C++ per archiviare i dati (usando R_ExternalPtr). I dati possono quindi essere elaborati utilizzando varie funzioni. Ecco un semplice esempio (la reale struttura di dati è molto più complessa):Come gestire la struttura dati interna C++ in R per consentire il salvataggio/caricamento?

#include <Rinternals.h> 
class MyObject{ 
     public: 
     int type; 
     MyObject(int t):type(t){} 
}; 


void finalizeMyObject(SEXP ptr){ 
    MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(ptr)); 
    delete mo; 
} 

extern "C" { 

    SEXP CreateObject(SEXP type) { 
     SEXP ans; 
     int nb = length(type); 
     PROTECT(ans=allocVector(VECSXP, nb)); 
     int * typeP = INTEGER(type); 
     SEXP cl; 
     PROTECT(cl = allocVector(STRSXP, 1)); 
     SET_STRING_ELT(cl, 0, mkChar("myObject")); 
     for(int i=0; i<nb; i++){ 
      MyObject * mo = new MyObject(typeP[i]); 
      SEXP tmpObj = R_MakeExternalPtr(mo, R_NilValue, R_NilValue); 
      R_RegisterCFinalizerEx(tmpObj, (R_CFinalizer_t) finalizeMyObject, TRUE); 

      classgets(tmpObj, cl); 
      SET_VECTOR_ELT(ans, i, tmpObj);//Put in vector 
     } 
     UNPROTECT(2); 
     return ans; 
    } 

    SEXP printMyObject(SEXP myObjects){ 
     int nb = length(myObjects); 
     for(int i=0; i<nb; i++){ 
      SEXP moS=VECTOR_ELT(myObjects, i); 
      MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(moS)); 
      Rprintf("%d\n", mo->type); 
     } 
     return R_NilValue; 
    } 
} 

volta che la struttura dati interna è costruita, diverse funzioni possono essere chiamati a calcolare diverse statistiche. La funzione di cui sopra printMyObject offre un esempio di tale funzione.

Il problema sorge quando si tenta di salvare questa struttura interna. Sembra salvare gli indirizzi del puntatore. Quando gli oggetti vengono ricaricati, otteniamo un segfault. Ecco un esempio di codice R (si supponga che myobject.cpp contiene il codice di cui sopra ed è stato compilato per myobject.dll)

dyn.load("myobject.dll") 
## Create the internal data structure 
xx <- .Call("CreateObject", 1:10) 
##Everything is fine 
.Call("printMyObject", xx) 
## Save it, no errors 
save(xx, file="xx.RData") 
## remove all objects 
rm(list=ls()) 
## Load the data 
load(file="xx.RData") 
##segfault 
.Call("printMyObject", xx) 

La mia domanda è: qual è il modo migliore per gestire questa situazione in modo corretto? Ho pensato ad alcune strategie, ma ad eccezione del primo, non so come può essere fatto (e se è possibile farlo):

  • Non restituire l'oggetto interno. Gli utenti hanno sempre strutture R che vengono convertite internamente (per ogni chiamata di funzione, ad esempio stampa e così via) alla struttura interna del C++. Pro: i dati possono essere salvati senza segfault. Contro: questo è molto inefficiente soprattutto quando ci sono molti dati.
  • Archiviare l'oggetto come struttura R e C++ e provare a capire (non so come) se la struttura C++ è danneggiata per ricostruirla. Vantaggi: la struttura dei dati interna viene creata solo una volta per sessione. Contro: non il migliore per problemi di memoria (i dati sono archiviati due volte).
  • Utilizzare solo la struttura C++ e mai salvare/caricare (soluzione effettiva utilizzata). Il problema è che non ci sono messaggi di errore, ma solo un segfault.
  • Trova un modo per serializzare l'oggetto C++ interno quando vengono utilizzate le funzioni di salvataggio/caricamento.

Qualsiasi idea/suggerimento è molto gradita.

+0

Non che ci sono ganci per questo. Mi piacerebbe avere una soluzione per questo problema. I moduli Rcpp usano molti puntatori esterni e al momento non sappiamo come gestire la persistenza. –

+0

Hai provato ad agganciare un debugger per vedere quale riga nel codice C++ ti dà il segfault? Ispezionando i riferimenti (null?) Prima e dopo il caricamento? Impostazione di un punto di interruzione per vedere quando viene eseguito quel codice di finalizzazione? Forse stai liberando la memoria prima della chiamata per il salvataggio, ma è ancora rimasta intatta (per il momento), quindi la funzione printMyObject funziona. – cachance7

+0

Il segmento predefinito viene dalla riga 'Rprintf ("% d \ n ", mo-> type);' ma solo la seconda volta. Questo è previsto, perché la struttura dei dati non viene salvata e quindi non ricaricata in seguito. Dopo il caricamento, il puntatore mo non è corretto, fornendo un segfault –

risposta

3

Infine, sono arrivato a 2 soluzioni per gestire correttamente il meccanismo di salvataggio/caricamento R con oggetti C++ (persistenza dei dati). Nessuno è perfetto, ma sembra meglio che non fare nulla. In breve, tali soluzioni sono (i dettagli sono disponibili di seguito):

  • Utilizzo di R Tipo di dati raw per memorizzare gli oggetti C++. Questo ha il vantaggio di essere efficiente in termini di memoria, ma è piuttosto complesso nella pratica. Personalmente, non lo userò per un grande progetto, perché la gestione della memoria può essere noiosa. Di sicuro, ci sono alcuni miglioramenti possibili usando le classi template (si prega di condividerlo, se avete un'idea).
  • Archiviare l'oggetto come struttura R e C++ e ricostruire gli oggetti C++ dopo il ricaricamento. L'oggetto C++ viene creato una sola volta (non per ogni chiamata di funzione), ma i dati vengono archiviati due volte. Questo ha il serio vantaggio di essere molto più facile da implementare su un grande progetto.

Per presentare queste soluzioni, utilizzo un esempio leggermente più complicato per evidenziare la complessità della prima soluzione. In questo esempio, il nostro esempio di oggetto C++ è una sorta di elenco collegato.

Prima soluzione: dati Usa R RAW

L'idea (proposta da @KarlForner) è quello di utilizzare il tipo di dati R grezzo per memorizzare il contenuto dell'oggetto.In pratica ciò significa:

  • Allocazione della memoria con R Tipo di dati non elaborati.
  • Assicurarsi di assegnare la quantità corretta di memoria (potrebbe essere complicato).
  • Utilizzare il nuovo posizionamento per creare l'oggetto C++ all'indirizzo di memoria specificato (quello con i dati non elaborati).

Ecco il file “myobject.cpp”:

#include <Rinternals.h> 
#include <new> 
//Our example Object: A linked list. 
class MyRawObject{ 
    int type; 
    // Next object in the list. 
    bool hasnext; 
    public: 
     MyRawObject(int t):type(t), hasnext (false){ 
      if(t>1){ 
       //We build a next object. 
       hasnext = true; 
       //Here we use placement new to build the object in the next allocated memory 
       // No need to store a pointer. 
      //We know that this is in the next contiguous location (see memory allocation below) 
      new (this+1) MyRawObject(t-1); 
      } 
     } 

     void print(){ 
      Rprintf(" => %d ", type); 
      if(this->hasnext){ 
       //Next object located is in the next contiguous memory block 
       (this+1)->print(); //Next in allocated memory 
      } 
     } 
}; 
extern "C" { 
    SEXP CreateRawObject(SEXP type) { 
     SEXP ans; 
     int nb = length(type); 
     PROTECT(ans=allocVector(VECSXP, nb)); 
     int * typeP = INTEGER(type); 
     //When allocating memory, we need to know the size of our object. 
     int MOsize =sizeof(MyRawObject); 
     for(int i=0; i<nb; i++){ 
      SEXP rawData; 
      //Allocate the memory using R RAW data type. 
      //Take care to allocate the right amount of memory 
       //Here we have typeP[i] objects to create. 
      PROTECT(rawData=allocVector(RAWSXP, typeP[i]*MOsize)); 
      //Memory address of the allocated memory 
      unsigned char * buf = RAW(rawData); 
      //Here we use placement new to build the object in the allocated memory 
      new (buf) MyRawObject(typeP[i]); 
      SET_VECTOR_ELT(ans, i, rawData);//Put in vector 
      UNPROTECT(1); 
     } 
     UNPROTECT(1); 
     return ans; 
    } 
    SEXP printRawObject(SEXP myObjects){ 
     int nb = length(myObjects); 
     for(int i=0; i<nb; i++){ 
      SEXP moS=VECTOR_ELT(myObjects, i); 
      //Use reinterpret_cast to have a pointer of type MyRawObject pointing to the RAW data 
      MyRawObject * mo= reinterpret_cast<MyRawObject *>(RAW(moS)); 
      if(mo!=NULL){ 
       Rprintf("Address: %d", mo); 
       mo->print(); 
       Rprintf("\n"); 
      }else{ 
       Rprintf("Null pointer!\n"); 
      } 
     } 
     return R_NilValue; 
    } 
} 

Le funzioni possono essere utilizzate direttamente in R per esempio così:

## Create the internal data structure 
xx <- .Call("CreateRawObject", 1:10) 
##Print 
.Call("printRawObject", xx) 
## Save it 
save(xx, file="xxRaw.RData") 
## remove all objects 
rm(xx) 
## Load the data 
load(file="xxRaw.RData") 
##Works ! 
.Call("printRawObject", xx) 

Ci sono diversi problemi con questa soluzione :

  • I distruttori non vengono chiamati (a seconda di cosa si sta facendo n il distruttore, questo potrebbe essere un problema).
  • Se la tua classe contiene un puntatore ad un altro oggetto, tutti i puntatori devono essere relativi alla posizione di memoria per farlo funzionare dopo il ricaricamento (come qui con l'elenco collegato).
  • È piuttosto complesso.
  • Non sono sicuro che il file salvato (.RData) sarà multipiattaforma (sarebbe sicuro condividere un file RData sui computer?), Perché non sono sicuro che la memoria utilizzata per archiviare un oggetto sarà lo stesso su tutte le piattaforme.

Seconda soluzione: archiviare l'oggetto come struttura R e C++ e ricostruire gli oggetti C++ dopo il ricaricamento.

L'idea è di verificare, in ogni chiamata di funzione, se i puntatori C++ sono corretti. In caso contrario, ricostruire l'oggetto, altrimenti ignorare il passaggio. Ciò è reso possibile dal fatto che stiamo modificando direttamente (in C++) l'oggetto R. Quindi la modifica sarà effettiva per tutte le chiamate successive. Il vantaggio è che gli oggetti C++ vengono creati una sola volta (non per ogni chiamata di funzione), ma i dati vengono archiviati due volte. Questo ha il serio vantaggio di essere molto più facile da implementare su un grande progetto.

Nel file "myobject.cpp".

#include <Rinternals.h> 
//Our object can be made simpler because we can use pointers 
class MyObject{ 
    int type; 
    //Pointer to the next object 
    MyObject *next; 
    public: 
     MyObject(int t):type(t), next(NULL){ 
      if(t>1){ 
       next = new MyObject(t-1); 
      } 
     } 
     ~MyObject(){ 
      if(this->next!=NULL){ 
       delete next; 
      } 
     } 
     void print(){ 
      Rprintf(" => %d ", type); 
      if(this->next!=NULL){ 
       this->next->print(); 
      } 
     } 
}; 
void finalizeMyObject(SEXP ptr){ 
    MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(ptr)); 
    delete mo; 
} 

extern "C" { 

    SEXP CreateObject(SEXP type) { 
     SEXP ans; 
     int nb = length(type); 
     PROTECT(ans=allocVector(VECSXP, nb)); 
     int * typeP = INTEGER(type); 
     SEXP cl; 
     PROTECT(cl = allocVector(STRSXP, 1)); 
     SET_STRING_ELT(cl, 0, mkChar("myObject")); 
     for(int i=0; i<nb; i++){ 
      MyObject * mo = new MyObject(typeP[i]); 
      SEXP tmpObj = R_MakeExternalPtr(mo, R_NilValue, R_NilValue); 
      R_RegisterCFinalizerEx(tmpObj, (R_CFinalizer_t) finalizeMyObject, TRUE); 

      classgets(tmpObj, cl); 
      SET_VECTOR_ELT(ans, i, tmpObj);//Put in vector 
     } 
     UNPROTECT(2); 
     return ans; 
    } 

    SEXP printMyObject(SEXP myObjects){ 
     int nb = length(myObjects); 
     for(int i=0; i<nb; i++){ 
      SEXP moS=VECTOR_ELT(myObjects, i); 
      MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(moS)); 
      if(mo!=NULL){ 
       Rprintf("Address: %d", mo); 
       mo->print(); 
       Rprintf("\n"); 
      }else{ 
       Rprintf("Null pointer!\n"); 
      } 
     } 
     return R_NilValue; 
    } 
    //This function check if a C++ object is NULL, if it is, it rebuilds all the C++ objects. 
    SEXP checkRebuildMyObject(SEXP myObjects, SEXP type){ 
     int nb = length(myObjects); 
     if(nb==0){ 
      return R_NilValue; 
     } 
     if(R_ExternalPtrAddr(VECTOR_ELT(myObjects, 1))){ //Non corrupted ptrs 
      return R_NilValue; 
     } 
     int * typeP = INTEGER(type); 
     SEXP cl; 
     PROTECT(cl = allocVector(STRSXP, 1)); 
     SET_STRING_ELT(cl, 0, mkChar("myObject")); 
     for(int i=0; i<nb; i++){ 
      MyObject * mo = new MyObject(typeP[i]); 
      SEXP tmpObj = R_MakeExternalPtr(mo, R_NilValue, R_NilValue); 
      R_RegisterCFinalizerEx(tmpObj, (R_CFinalizer_t) finalizeMyObject, TRUE); 
      classgets(tmpObj, cl); 
      SET_VECTOR_ELT(myObjects, i, tmpObj);//Put in vector 
     } 
     UNPROTECT(1); 
     return R_NilValue; 
    } 

} 

Nel lato R, possiamo usare quelle funzioni come seguono:

dyn.load("myobject.dll") 

CreateObjectR <- function(type){ 
## We use a custom object type, that store both C++ and R data 
type <- as.integer(type) 
mo <- list(type=type, myObject= .Call("CreateObject", type)) 
class(mo) <- "myObjectList" 
return(mo) 
} 

print.myObjectList <- function(x, ...){ 
## Be sure to check the C++ objects before calling the functions. 
.Call("checkRebuildMyObject", x$myObject, x$type) 
.Call("printMyObject", x$myObject) 
invisible() 
} 

xx <- CreateObjectR(1:10) 
print(xx) 
save(xx, file="xx.RData") 
rm(xx) 
load(file="xx.RData") 
print(xx) 
Problemi correlati