Dove posso imparare a scrivere codice C per accelerare le funzioni R lente? [chiuso]


115

Qual è la migliore risorsa per imparare a scrivere codice C da utilizzare con R? Conosco la sezione delle interfacce di sistema e delle lingue straniere delle estensioni R, ma trovo che sia piuttosto difficile. Quali sono le buone risorse (sia online che offline) per scrivere codice C da utilizzare con R?

Per chiarire, non voglio imparare a scrivere codice C, voglio imparare a integrare meglio R e C.Ad esempio, come faccio a convertire da un vettore intero C a un vettore intero R (o viceversa) o da uno scalare C a un vettore R?

Risposte:


71

Bene, c'è il buon vecchio Usa la fonte, Luke! --- Lo stesso R ha un sacco di codice C (molto efficiente) che si può studiare e CRAN ha centinaia di pacchetti, alcuni di autori di cui ti fidi. Ciò fornisce esempi reali e testati da studiare e adattare.

Ma come sospettava Josh, mi avvicino maggiormente al C ++ e quindi a Rcpp . Ha anche molti esempi.

Modifica: c'erano due libri che ho trovato utili:

  • Il primo è " S Programming " di Venables e Ripley, anche se sta diventando troppo lungo (e ci sono state voci di una seconda edizione da anni). A quel tempo non c'era semplicemente nient'altro.
  • Il secondo nel " Software per l'analisi dei dati " di Chambers, che è molto più recente e ha un aspetto R-centrico molto più gradevole - e due capitoli sull'estensione di R. Vengono menzionati sia C che C ++. Inoltre, John mi fa a pezzi per quello che ho fatto con digest in modo che da solo valga il prezzo dell'ammissione.

Detto questo, John si sta appassionando a Rcpp (e contribuisce) poiché trova che la corrispondenza tra oggetti R e oggetti C ++ (tramite Rcpp ) sia molto naturale e ReferenceClasses aiuta.

Modifica 2: con la domanda rifocalizzata di Hadley, ti esorto vivamente a considerare il C ++. Ci sono così tante sciocchezze che hai a che fare con C --- molto noioso e molto evitabile . Dai un'occhiata alla vignetta introduttiva di Rcpp . Un altro semplice esempio è questo post del blog in cui mostro che invece di preoccuparci di differenze del 10% (in uno degli esempi di Radford Neal) possiamo ottenere aumenti di ottanta volte con C ++ (su quello che è ovviamente un esempio artificioso).

Modifica 3: c'è complessità in quanto potresti incappare in errori C ++ che sono, per usare un eufemismo, difficili da risolvere. Ma per usare solo Rcpp piuttosto che estenderlo, non dovresti quasi mai averne bisogno. E sebbene questo costo sia innegabile, è di gran lunga eclissato dal vantaggio di un codice più semplice, meno boilerplate, nessuna protezione / UNPROTECT, nessuna gestione della memoria ecc. Pp. Doug Bates proprio ieri ha dichiarato che trova C ++ e Rcpp molto più simili a scrivere R che scrivere C ++. YMMV e tutto il resto.


Mi aspettavo di ottenere una risposta "usa Rcpp";) Sarebbe davvero utile se potessi spiegare gli svantaggi dell'utilizzo di C ++ invece di C. Uno dei principali sembrerebbe essere che C ++ è molto più complesso di C - questo lo rende più difficile da usare? (O in pratica, puoi scrivere codice C ++ molto simile a C?) Apprezzerei anche più materiale di riferimento rivolto a nuovi utenti che non hanno familiarità con l'API C esistente.
Hadley

2
Vedi Modifica 3 e sì, puoi . Meyers chiama C ++ un linguaggio dei "quattro paradigmi" e non è necessario utilizzarli tutti e quattro. Usarlo come 'solo una C migliore' e usare Rcpp come colla per R va perfettamente bene. Nessuno ti impone uno stile - questo non è Java ;-)
Dirk Eddelbuettel

@Dirk: grazie per l'elaborazione. Ha sollevato la questione nel nostro ufficio prima, poiché C è comunemente usato qui invece di C ++. Quando sarebbe vantaggioso l'uso di C su C ++ o dici semplicemente "mai C, sempre C ++"?
Joris Meys,

Hadley: Fantastico. Saremmo molto interessati al tuo feedback. Per favore, unisciti a rcpp-devel e non trattenerti. Sappiamo di essere una documentazione breve, ma una nuova occhiata potrebbe aiutare enormemente.
Dirk Eddelbuettel

6
@hadley significa che potremmo aspettarci dei miglioramenti di velocità ggplot?
aL3xa

56

Hadley,

Puoi sicuramente scrivere codice C ++ simile al codice C.

Capisco cosa dici sul fatto che C ++ sia più complicato di C.Questo è se vuoi padroneggiare tutto: oggetti, modelli, STL, meta-programmazione di modelli, ecc ... la maggior parte delle persone non ha bisogno di queste cose e può semplicemente fare affidamento sugli altri ad esso. L'implementazione di Rcpp è molto complicata, ma solo perché non sai come funziona il tuo frigorifero, non significa che non puoi aprire la porta e prendere il latte fresco ...

Dai tuoi numerosi contributi a R, ciò che mi colpisce è che trovi R piuttosto noioso (manipolazione dei dati, grafica, manipolazione delle stringhe, ecc ...). Preparati a molte altre sorprese con l'API C interna di R. Questo è molto noioso.

Di tanto in tanto, leggo i manuali R-exts o R-ints. Questo aiuta. Ma la maggior parte delle volte, quando voglio veramente scoprire qualcosa, vado nel sorgente R, e anche nel sorgente dei pacchetti scritti ad esempio da Simon (di solito c'è molto da imparare lì).

Rcpp è progettato per eliminare questi noiosi aspetti dell'API.

Puoi giudicare da solo ciò che trovi più complicato, offuscato, ecc ... sulla base di alcuni esempi. Questa funzione crea un vettore di caratteri utilizzando l'API C:

SEXP foobar(){
  SEXP ab;
  PROTECT(ab = allocVector(STRSXP, 2));
  SET_STRING_ELT( ab, 0, mkChar("foo") );
  SET_STRING_ELT( ab, 1, mkChar("bar") );
  UNPROTECT(1);
}

Usando Rcpp, puoi scrivere la stessa funzione di:

SEXP foobar(){
   return Rcpp::CharacterVector::create( "foo", "bar" ) ;
}

o:

SEXP foobar(){
   Rcpp::CharacterVector res(2) ;
   res[0] = "foo" ;
   res[1] = "bar" ;
   return res ;
}

Come ha detto Dirk, ci sono altri esempi sulle diverse vignette. Inoltre, di solito indirizziamo le persone verso i nostri test unitari perché ognuno di loro testa una parte molto specifica del codice e sono in qualche modo autoesplicativi.

Ovviamente sono di parte qui, ma consiglierei di familiarizzare con Rcpp invece di imparare l'API C di R, e poi venire alla mailing list se qualcosa non è chiaro o non sembra fattibile con Rcpp.

Comunque, fine del discorso di vendita.

Immagino che tutto dipenda dal tipo di codice che vuoi scrivere alla fine.

Romain


2
"Rcpp è progettato per eliminare questi noiosi aspetti dell'API" = esattamente quello che sto cercando. Grazie! Ciò che sarebbe veramente utile sarebbe un breve introduzione al C ++ per qualcuno che ha familiarità con C e vuole usare Rcpp.
Hadley

bello, quel breve esempio di Rcpp mi ha fatto vendere. Suppongo che allocXX e UNPROTECT (1) siano gestiti in modo molto simile a come i puntatori intelligenti gestiscono la risorsa. cioè RAII. C'è qualche notevole penalizzazione delle prestazioni usando Rcpp su vanilla C api?
jbremnant

Lo affrontiamo nell'introduzione di Rcpp con un esempio di benchmark (che è anche nel pacchetto sorgenti / installato). Insomma, nessuna penalità.
Dirk Eddelbuettel

29

@hadley: sfortunatamente, non ho in mente risorse specifiche per aiutarti a iniziare a usare C ++. L'ho preso dai libri di Scott Meyers (C ++ efficace, C ++ più efficace, ecc ...) ma questi non sono proprio ciò che si potrebbe chiamare introduttivo.

Usiamo quasi esclusivamente l'interfaccia .Call per chiamare il codice C ++. La regola è abbastanza semplice:

  • La funzione C ++ deve restituire un oggetto R. Tutti gli oggetti R sono SEXP.
  • La funzione C ++ accetta tra 0 e 65 oggetti R come input (di nuovo SEXP)
  • si deve (non proprio, ma siamo in grado di salvare questo per dopo) essere dichiarata con collegamento C, sia con extern "C" o RcppExport alias che definisce Rcpp.

Quindi una funzione .Call viene dichiarata in questo modo in un file di intestazione:

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

e implementato in questo modo in un file .cpp:

SEXP foo( SEXP x1, SEXP x2 ){
   ...
}

Non c'è molto altro da sapere sull'API R per utilizzare Rcpp.

La maggior parte delle persone vuole trattare solo con vettori numerici in Rcpp. Puoi farlo con la classe NumericVector. Esistono diversi modi per creare un vettore numerico:

Da un oggetto esistente che passi da R:

 SEXP foo( SEXP x_) {
    Rcpp::NumericVector x( x_ ) ;
    ...
 }

Con valori dati utilizzando la funzione :: create static:

 Rcpp::NumericVector x = Rcpp::NumericVector::create( 1.0, 2.0, 3.0 ) ;
 Rcpp::NumericVector x = Rcpp::NumericVector::create( 
    _["a"] = 1.0, 
    _["b"] = 2.0, 
    _["c"] = 3
 ) ;

Di una determinata dimensione:

 Rcpp::NumericVector x( 10 ) ;      // filled with 0.0
 Rcpp::NumericVector x( 10, 2.0 ) ; // filled with 2.0

Quindi, una volta che hai un vettore, la cosa più utile è estrarne un elemento. Questo viene fatto con l'operatore [], con l'indicizzazione basata su 0, quindi per esempio la somma dei valori di un vettore numerico è qualcosa del genere:

SEXP sum( SEXP x_ ){
   Rcpp::NumericVector x(x_) ;
   double res = 0.0 ;
   for( int i=0; i<x.size(), i++){
      res += x[i] ;
   }
   return Rcpp::wrap( res ) ;
}

Ma con lo zucchero Rcpp possiamo farlo molto più bene ora:

using namespace Rcpp ;
SEXP sum( SEXP x_ ){
   NumericVector x(x_) ;
   double res = sum( x ) ;
   return wrap( res ) ;
}

Come ho detto prima, tutto dipende dal tipo di codice che vuoi scrivere. Guarda cosa fanno le persone nei pacchetti che si basano su Rcpp, controlla le vignette, gli unit test, torna da noi sulla mailing list. Siamo sempre felici di aiutarti.


20

@jbremnant: Esatto. Le classi Rcpp implementano qualcosa di simile al pattern RAII. Quando viene creato un oggetto Rcpp, il costruttore prende le misure appropriate per garantire che l'oggetto R sottostante (SEXP) sia protetto dal Garbage Collector. Il distruttore ritira la protezione. Questo è spiegato nella vignetta di introduzione Rcpp . L'implementazione sottostante si basa sulle funzioni API R R_PreserveObject e R_ReleaseObject

C'è effettivamente una riduzione delle prestazioni a causa dell'incapsulamento C ++. Cerchiamo di mantenerlo al minimo con inlining, ecc ... La penalità è piccola, e quando si tiene conto del guadagno in termini di tempo necessario per scrivere e mantenere il codice, non è così rilevante.

La chiamata di funzioni R dalla funzione di classe Rcpp è più lenta della chiamata diretta di eval con l'api C. Questo perché prendiamo precauzioni e racchiudiamo la chiamata di funzione in un blocco tryCatch in modo da acquisire gli errori R e promuoverli a eccezioni C ++ in modo che possano essere gestiti utilizzando lo standard try / catch in C ++.

La maggior parte delle persone desidera utilizzare i vettori (specialmente NumericVector) e la penalità è molto piccola con questa classe. La directory examples / ConvolveBenchmarks contiene diverse varianti della famigerata funzione di convoluzione di R-exts e la vignetta ha risultati di benchmark. Si scopre che Rcpp lo rende più veloce del codice benchmark che utilizza l'API R.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.