Waar kan ik leren hoe ik C-code moet schrijven om langzame R-functies te versnellen?

Wat is de beste bron om te leren hoe je C-code schrijft voor gebruik met R? Ik weet van de systeem- en vreemde-taalinterfacessectie van R-extensies, maar ik vind het behoorlijk moeilijk om te gaan. Wat zijn goede bronnen (zowel online als offline) voor het schrijven van C-code voor gebruik met R?

Ter verduidelijking: ik wil niet leren hoe ik C-code moet schrijven, ik wil leren hoe ik R en C beter kan integreren. Hoe converteer ik bijvoorbeeld van een geheel getal C naar een geheel getal R (of vice versa) of van een C-scalar naar een R-vector?


Antwoord 1, autoriteit 100%

Nou, daar is de goede oude Gebruik de bron, Luke!— R zelf heeft genoeg (zeer efficiënte) C-code die men kan bestuderen, en CRAN heeft honderden pakketten, sommige van auteurs jij vertrouwt. Dat levert echte, beproefde voorbeelden op om te bestuderen en aan te passen.

Maar zoals Josh al vermoedde, neig ik meer naar C++ en daarom Rcpp. Het heeft ook tal van voorbeelden.

Bewerken:Er waren twee boeken die ik nuttig vond:

  • De eerste is Venables en Ripley’s “S Programming“, hoewel het lang op zich laat wachten (en er gaan al jaren geruchten over een 2e editie). Destijds was er gewoon niets anders.
  • De tweede in Chambers’ “Software for Data Analysis“, die veel recenter is en een veel prettiger R-gecentreerd gevoel heeft — en twee hoofdstukken over het uitbreiden van R. Zowel C als C++ worden genoemd . Bovendien verscheurt John me voor wat ik deed met digest, dus dat alleen al is de prijs waard van toelating.

Dat gezegd hebbende, John is dol op Rcpp(en draagt ​​bij) zoals hij vindt de overeenkomst tussen R-objecten en C++-objecten (via Rcpp) om heel natuurlijk te zijn — en ReferenceClasses helpt daarbij.

Bewerken 2:Met de herschikte vraag van Hadley, raad ik u zeer sterkaan om C++ te overwegen. Er is zoveel standaard onzin die je met C te maken hebt — erg vervelend en zeer vermijdbaar. Bekijk het Rcpp-introductievignet. Een ander eenvoudig voorbeeld is deze blogpostwaar ik dat laat zien in plaats van me zorgen te maken over 10% verschillen (in een van de Radford Neal-voorbeelden) we kunnen tachtigvoudigeverhogingen krijgen met C++ (op wat natuurlijk een gekunsteld voorbeeld is).

Bewerken 3:Het is ingewikkeld omdat je C++-fouten kunt tegenkomen die op zijn zachtst gezegd moeilijk te kraken zijn. Maar om gewoon Rcppte gebruiken in plaats van het uit te breiden, zou je het bijna nooit nodig hebben. En hoewel deze kostonmiskenbaar is, wordt het veel overschaduwd door het voordeelvan eenvoudigere code, minder standaardtekst, geen PROTECT/UNPROTECT, geen geheugenbeheer enz. pp. Doug Bates gisteren verklaarde dat hij vindt dat C++ en Rcpp veel meer lijken op het schrijven van R dan op het schrijven van C++. YMMV en zo.


Antwoord 2, autoriteit 79%

Hadley,

Je kunt zeker C++-code schrijven die vergelijkbaar is met C-code.

Ik begrijp wat je zegt dat C++ ingewikkelder is dan C. Dit is als je alles onder de knie wilt hebben: objecten, sjablonen, STL, sjabloonmeta-programmering, enz … de meeste mensen hebben deze dingen niet nodig en kunnen gewoon daarvoor op anderen vertrouwen. De implementatie van Rcpp is erg ingewikkeld, maar alleen omdat je niet weet hoe je koelkast werkt, betekent dit niet dat je de deur niet kunt openen en verse melk kunt pakken …

Uit je vele bijdragen aan R valt me ​​op dat je R een beetje vervelend vindt (gegevensmanipulatie, afbeeldingen, manipulatie van strings, enz …). Wees voorbereid op nog veel meer verrassingen met de interne C API van R. Dit is erg vervelend.

Van tijd tot tijd lees ik de R-exts of R-ints handleidingen. Dit helpt. Maar meestal, als ik echt iets wil weten, ga ik naar de R-bron, en ook naar de bron van pakketten die zijn geschreven door b.v. Simon (er valt daar meestal veel te leren).

Rcpp is ontworpen om deze vervelende aspecten van de API te laten verdwijnen.

Je kunt zelf beoordelen wat je ingewikkelder, versluierd, enz. vindt … op basis van een paar voorbeelden. Deze functie maakt een tekenvector met behulp van de C API:

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

Met Rcpp kun je dezelfde functie schrijven als:

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

of:

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

Zoals Dirk al zei, zijn er andere voorbeelden op de verschillende vignetten. We verwijzen mensen meestal ook naar onze unit-tests omdat elk van hen een heel specifiek deel van de code test en enigszins voor zichzelf spreekt.

Ik ben hier duidelijk bevooroordeeld, maar ik zou aanraden om vertrouwd te raken met Rcpp in plaats van de C API van R te leren, en dan naar de mailinglijst te gaan als iets onduidelijk is of niet haalbaar lijkt met Rcpp.

Hoe dan ook, einde van het verkooppraatje.

Ik denk dat het allemaal afhangt van wat voor soort code je uiteindelijk wilt schrijven.

Romain


Antwoord 3, autoriteit 41%

@hadley: helaas heb ik geen specifieke bronnen in gedachten om je op weg te helpen met C++. Ik heb het opgepikt uit de boeken van Scott Meyers (Effective C++, More Effective C++, enz …) maar dit zijn niet echt wat je inleidend zou kunnen noemen.

We gebruiken bijna uitsluitend de .Call-interface om C++-code aan te roepen. De regel is eenvoudig genoeg:

  • De C++-functie moet een R-object teruggeven. Alle R-objecten zijn SEXP.
  • De C++-functie neemt tussen 0 en 65 R-objecten als invoer (opnieuw SEXP)
  • het moet (niet echt, maar we kunnen dit voor later bewaren) gedeclareerd worden met C-linkage, ofwel met externe “C”of de RcppExportalias die Rcpp definieert .

Dus een .Call-functie wordt als volgt gedeclareerd in een headerbestand:

#include <Rcpp.h>
RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

en op deze manier geïmplementeerd in een .cpp-bestand:

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

Er is niet veel meer te weten over de R API om Rcpp te gebruiken.

De meeste mensen willen alleen omgaan met numerieke vectoren in Rcpp. Dit doe je met de klasse NumericVector. Er zijn verschillende manieren om een ​​numerieke vector te maken:

Van een bestaand object dat u doorgeeft vanuit R:

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

Met gegeven waarden met behulp van de ::create statische functie:

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

Van een bepaalde maat:

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

Als je eenmaal een vector hebt, is het het handigst om er één element uit te extraheren. Dit wordt gedaan met de operator [], met indexering op basis van 0, dus het optellen van waarden van een numerieke vector gaat ongeveer als volgt:

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

Maar met Rcpp-suiker kunnen we dit nu veel beter doen:

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

Zoals ik al eerder zei, het hangt allemaal af van wat voor soort code je wilt schrijven. Kijk naar wat mensen doen in pakketten die afhankelijk zijn van Rcpp, controleer de vignetten, de unit-tests, kom terug naar ons op de mailinglijst. We zijn altijd blij om te helpen.


Antwoord 4, autoriteit 27%

@jbremnant: Dat klopt. Rcpp-klassen implementeren iets dat dicht bij het RAII-patroon ligt. Wanneer een Rcpp-object wordt gemaakt, neemt de constructor passende maatregelen om ervoor te zorgen dat het onderliggende R-object (SEXP) wordt beschermd tegen de garbage collector. De destructor trekt de bescherming in. Dit wordt uitgelegd in het Rcpp-intrductionvignet . De onderliggende implementatie is gebaseerd op de R API-functies R_PreserveObjecten R_ReleaseObject

Er is inderdaad prestatieverlies vanwege C++-inkapseling. We proberen dit tot een minimum te beperken met inlining, enz… De boete is klein, en als je rekening houdt met de tijdwinst die het kost om code te schrijven en te onderhouden, is dat niet zo relevant.

Het aanroepen van R-functies uit de Rcpp-klasse Function is langzamer dan het rechtstreeks aanroepen van eval met de C api. Dit komt omdat we voorzorgsmaatregelen nemen en de functieaanroep in een tryCatch-blok verpakken, zodat we R-fouten vastleggen en ze promoveren naar C++-uitzonderingen, zodat ze kunnen worden afgehandeld met behulp van de standaard try/catch in C++.

De meeste mensen willen vectoren gebruiken (speciaal NumericVector), en de straf is erg klein met deze klasse. De directory voorbeelden/ConvolveBenchmarks bevat verschillende varianten van de beruchte convolutiefunctie van R-exts en het vignet heeft benchmarkresultaten. Het blijkt dat Rcpp het sneller maakt dan de benchmarkcode die de R API gebruikt.

Other episodes