Wo kann ich lernen, wie man C-Code schreibt, um langsame R-Funktionen zu beschleunigen? [geschlossen]

115

Was ist die beste Ressource, um zu lernen, wie man C-Code zur Verwendung mit R schreibt? Ich kenne den Abschnitt über System- und Fremdsprachenschnittstellen in R-Erweiterungen, finde es aber ziemlich schwierig. Was sind gute Ressourcen (sowohl online als auch offline) zum Schreiben von C-Code zur Verwendung mit R?

Zur Verdeutlichung möchte ich nicht lernen, wie man C-Code schreibt, sondern wie man R und C besser integriert. Zum Beispiel, wie ich von einem C-Ganzzahlvektor in einen R-Ganzzahlvektor konvertiere (oder umgekehrt). oder von einem C-Skalar zu einem R-Vektor?

Hadley
quelle

Antworten:

71

Nun, da ist der gute alte. Benutze die Quelle, Luke! --- R selbst hat viel (sehr effizienten) C-Code, den man studieren kann, und CRAN hat Hunderte von Paketen, einige von Autoren, denen Sie vertrauen. Das liefert echte, getestete Beispiele zum Studieren und Anpassen.

Aber wie Josh vermutet hat, neige ich mehr zu C ++ und damit zu Rcpp . Es gibt auch viele Beispiele.

Edit: Es gab zwei Bücher, die ich hilfreich fand:

  • Das erste ist Venables und Ripleys " S Programming ", obwohl es immer länger wird (und es gibt Gerüchte über eine 2. Ausgabe seit Jahren). Zu der Zeit gab es einfach nichts anderes.
  • Die zweite in Chambers '" Software for Data Analysis ", die viel jünger ist und ein viel besseres R-zentriertes Gefühl hat - und zwei Kapitel über die Erweiterung von R. Sowohl C als auch C ++ werden erwähnt. Außerdem zerfetzt John mich für das, was ich mit Digest gemacht habe, so dass allein der Eintrittspreis wert ist.

Trotzdem liebt John Rcpp (und trägt dazu bei), da er die Übereinstimmung zwischen R-Objekten und C ++ - Objekten (über Rcpp ) als sehr natürlich ansieht - und ReferenceClasses helfen dort.

Edit 2: Mit Hadleys refokussierten Frage, ich sehr stark fordere Sie auf C ++ zu betrachten. Es gibt so viel Unsinn auf der Kesselplatte, dass Sie mit C zu tun haben - sehr langweilig und sehr vermeidbar . Schauen Sie sich die Rcpp-Einführungsvignette an . Ein weiteres einfaches Beispiel ist dieser Blog-Beitrag, in dem ich zeige, dass wir, anstatt uns um 10% Unterschiede zu sorgen (in einem der Radford Neal-Beispiele), mit C ++ eine achtzigfache Steigerung erzielen können (was natürlich ein erfundenes Beispiel ist).

Bearbeiten 3: Es besteht Komplexität darin, dass Sie möglicherweise auf C ++ - Fehler stoßen, die, gelinde gesagt, schwer zu verstehen sind. Aber um Rcpp nur zu verwenden, anstatt es zu erweitern, sollten Sie es kaum jemals brauchen. Und obwohl diese Kosten nicht zu leugnen sind, werden sie durch den Vorteil von einfacherem Code, weniger Boilerplate, keinem PROTECT / UNPROTECT, keiner Speicherverwaltung usw. in den Schatten gestellt. Doug Bates erklärte erst gestern, dass C ++ und Rcpp dem Schreiben von R viel ähnlicher sind als C ++ zu schreiben. YMMV und das alles.

Dirk Eddelbuettel
quelle
Ich hatte erwartet, dass ich eine "use Rcpp" -Antwort bekommen würde;) Es wäre wirklich nützlich, wenn Sie die Nachteile der Verwendung von C ++ anstelle von C darlegen könnten. Ein Hauptproblem scheint zu sein, dass C ++ viel komplexer ist als C - das macht es schwieriger zu bedienen? (Oder können Sie in der Praxis C ++ - Code schreiben, der C sehr ähnlich ist?) Ich würde mich auch über mehr Referenzmaterial freuen, das sich an neue Benutzer richtet, die mit der vorhandenen C-API nicht vertraut sind.
Hadley
2
Siehe Bearbeiten 3 und ja, Sie können . Meyers nennt C ++ eine Vier-Paradigmen-Sprache, und Sie müssen nicht alle vier verwenden. Es ist vollkommen in Ordnung, es als "nur ein besseres C" zu verwenden und Rcpp als Klebstoff für R zu verwenden. Niemand zwingt dir einen Stil auf - das ist nicht Java ;-)
Dirk Eddelbuettel
@Dirk: Danke für die Ausarbeitung. Dies hat die Frage in unserem Büro bereits aufgeworfen, da hier häufig C anstelle von C ++ verwendet wird. Wann würde die Verwendung von C über C ++ von Vorteil sein, oder sagen Sie einfach "nie C, immer C ++"?
Joris Meys
Hadley: Cool. Wir würden uns sehr über Ihr Feedback freuen. Bitte treten Sie rcpp-devel bei und halten Sie sich nicht zurück. Wir wissen, dass wir eine kurze Dokumentation sind - aber ein neuer Satz Augen könnte enorm helfen.
Dirk Eddelbuettel
6
@ Hadley bedeutet das, dass wir einige Geschwindigkeitsverbesserungen in erwarten können ggplot?
aL3xa
56

Hadley,

Sie können definitiv C ++ - Code schreiben, der dem C-Code ähnlich ist.

Ich verstehe, was Sie über C ++ sagen, das komplizierter als C ist. Dies ist, wenn Sie alles beherrschen möchten: Objekte, Vorlagen, STL, Vorlagen-Metaprogrammierung usw. Die meisten Menschen brauchen diese Dinge nicht und können sich einfach auf andere verlassen dazu. Die Implementierung von Rcpp ist sehr kompliziert, aber nur weil Sie nicht wissen, wie Ihr Kühlschrank funktioniert, heißt das nicht, dass Sie die Tür nicht öffnen und frische Milch holen können ...

Aus Ihren vielen Beiträgen zu R fällt mir auf, dass Sie R etwas langweilig finden (Datenmanipulation, Grafiken, String-Manipulation usw.). Bereiten Sie sich mit der internen C-API von R auf viele weitere Überraschungen vor. Dies ist sehr mühsam.

Von Zeit zu Zeit las ich die R-Exts- oder R-Ints-Handbücher. Das hilft. Aber die meiste Zeit, wenn ich wirklich etwas herausfinden möchte, gehe ich in die R-Quelle und auch in die Quelle von Paketen, die zB Simon geschrieben hat (dort gibt es normalerweise viel zu lernen).

Rcpp wurde entwickelt, um diese langwierigen Aspekte der API zu beseitigen.

Sie können anhand einiger Beispiele selbst beurteilen, was Sie komplizierter, verschleierter usw. finden. Diese Funktion erstellt mithilfe der C-API einen Zeichenvektor:

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);
}

Mit Rcpp können Sie dieselbe Funktion schreiben wie:

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

oder:

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

Wie Dirk sagte, gibt es andere Beispiele auf den verschiedenen Vignetten. Wir weisen die Leute normalerweise auch auf unsere Unit-Tests hin, da jeder von ihnen einen ganz bestimmten Teil des Codes testet und etwas selbsterklärend ist.

Ich bin hier offensichtlich voreingenommen, aber ich würde empfehlen, sich mit Rcpp vertraut zu machen, anstatt die C-API von R zu lernen, und dann zur Mailingliste zu kommen, wenn etwas unklar ist oder mit Rcpp nicht machbar erscheint.

Wie auch immer, Ende des Verkaufsgesprächs.

Ich denke, es hängt alles davon ab, welche Art von Code Sie irgendwann schreiben möchten.

Romain

Romain Francois
quelle
2
"Rcpp wurde entwickelt, um diese langwierigen Aspekte der API verschwinden zu lassen" = genau das, wonach ich suche. Vielen Dank! Was wirklich nützlich wäre, wäre ein v. Kurzer C ++ - Primer für jemanden, der mit C vertraut ist und Rcpp verwenden möchte.
Hadley
Schön, dieses kurze Beispiel von Rcpp hat mich verkauft. Ich gehe davon aus, dass allocXX und UNPROTECT (1) ähnlich behandelt werden, wie intelligente Zeiger die Ressource verwalten. dh RAII. Gibt es eine bemerkenswerte Leistungsbeeinträchtigung durch die Verwendung von Rcpp gegenüber Vanilla C api?
jbremnant
Wir sprechen dies in der Rcpp-Einführung mit einem Benchmark-Beispiel an (das sich auch im Quell- / Installationspaket befindet). Kurz gesagt, überhaupt keine Strafe.
Dirk Eddelbuettel
29

@hadley: Leider habe ich keine spezifischen Ressourcen im Sinn, um Ihnen den Einstieg in C ++ zu erleichtern. Ich habe es aus Scott Meyers 'Büchern (Effective C ++, Effective C ++ usw.) aufgegriffen, aber diese sind nicht wirklich das, was man als Einführung bezeichnen könnte.

Wir verwenden fast ausschließlich die .Call-Schnittstelle, um C ++ - Code aufzurufen. Die Regel ist einfach genug:

  • Die C ++ - Funktion muss ein R-Objekt zurückgeben. Alle R-Objekte sind SEXP.
  • Die C ++ - Funktion verwendet zwischen 0 und 65 R Objekte als Eingabe (wieder SEXP)
  • Es muss (nicht wirklich, aber wir können es für später speichern) mit C-Verknüpfung deklariert werden, entweder mit externem "C" oder dem von Rcpp definierten RcppExport- Alias.

Eine .Call-Funktion wird also in einer Header-Datei wie folgt deklariert:

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

und wie folgt in einer CPP-Datei implementiert:

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

Es gibt nicht viel mehr über die R-API zu wissen, die Rcpp verwendet.

Die meisten Leute wollen sich nur mit numerischen Vektoren in Rcpp befassen. Sie tun dies mit der NumericVector-Klasse. Es gibt verschiedene Möglichkeiten, einen numerischen Vektor zu erstellen:

Von einem vorhandenen Objekt, das Sie von R weitergeben:

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

Mit gegebenen Werten mit der Funktion :: 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
 ) ;

Von einer bestimmten Größe:

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

Sobald Sie einen Vektor haben, ist es am nützlichsten, ein Element daraus zu extrahieren. Dies geschieht mit dem Operator [] mit 0-basierter Indizierung, sodass beispielsweise das Summieren von Werten eines numerischen Vektors ungefähr so ​​aussieht:

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 ) ;
}

Aber mit Rcpp-Zucker können wir das jetzt viel besser machen:

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

Wie ich bereits sagte, hängt alles davon ab, welche Art von Code Sie schreiben möchten. Schauen Sie sich an, was Menschen in Paketen tun, die auf Rcpp basieren, überprüfen Sie die Vignetten, die Komponententests und melden Sie sich auf der Mailingliste bei uns. Wir helfen Ihnen gerne weiter.

Romain Francois
quelle
20

@jbremnant: Das stimmt. Rcpp-Klassen implementieren etwas in der Nähe des RAII-Musters. Wenn ein Rcpp-Objekt erstellt wird, ergreift der Konstruktor geeignete Maßnahmen, um sicherzustellen, dass das zugrunde liegende R-Objekt (SEXP) vor dem Garbage Collector geschützt ist. Der Destruktor zieht den Schutz zurück. Dies wird in der Rcpp-Intduktionsvignette erklärt . Die zugrunde liegende Implementierung basiert auf den R-API-Funktionen R_PreserveObject und R_ReleaseObject

Es gibt tatsächlich Leistungseinbußen aufgrund der C ++ - Kapselung. Wir versuchen, dies durch Inlining usw. auf ein Minimum zu beschränken. Die Strafe ist gering, und wenn Sie den Zeitgewinn berücksichtigen, der zum Schreiben und Verwalten von Code erforderlich ist, ist dies nicht so relevant.

Aufrufen von R-Funktionen aus der Rcpp-Klasse Function ist langsamer als das direkte Aufrufen von eval mit der C-API. Dies liegt daran, dass wir Vorsichtsmaßnahmen treffen und den Funktionsaufruf in einen tryCatch-Block einbinden, damit wir R-Fehler erfassen und zu C ++ - Ausnahmen heraufstufen, damit sie mit dem Standard-try / catch in C ++ behandelt werden können.

Die meisten Leute wollen Vektoren verwenden (speziell NumericVector), und die Strafe ist bei dieser Klasse sehr gering. Das Verzeichnis examples / ConvolveBenchmarks enthält mehrere Varianten der berüchtigten Faltungsfunktion von R-exts, und die Vignette enthält Benchmark-Ergebnisse. Es stellt sich heraus, dass Rcpp es schneller macht als der Benchmark-Code, der die R-API verwendet.

Romain Francois
quelle