112 votes

Où puis-je apprendre à écrire du code C pour accélérer les fonctions R lentes ?

Quelle est la meilleure ressource pour apprendre à écrire du code C à utiliser avec R ? Je connais le système et interfaces en langue étrangère section des extensions R, mais je trouve ça assez difficile. Quelles sont les bonnes ressources (en ligne et hors ligne) pour écrire du code C à utiliser avec R ?

Pour clarifier, je ne veux pas apprendre à écrire du code C, je veux apprendre à mieux intégrer R et C. Par exemple, comment convertir un vecteur entier C en vecteur entier R (ou vice versa) ou un scalaire C en vecteur R ?

68voto

Dirk Eddelbuettel Points 134700

Eh bien, il y a le bon vieux Utilise la source, Luke ! --- R lui-même a beaucoup de code C (très efficace) que l'on peut étudier, et CRAN a des centaines de paquets, certains d'auteurs en qui vous avez confiance. Cela fournit des exemples réels et testés à étudier et à adapter.

Mais comme Josh s'en doutait, je penche plutôt vers le C++ et donc Rcpp . Il contient également de nombreux exemples.

Editar: Il y a deux livres que j'ai trouvés utiles :

  • Le premier est celui de Venables et Ripley " S Programmation "même s'il commence à dater (et les rumeurs d'une deuxième édition courent depuis des années). À l'époque, il n'y avait tout simplement rien d'autre.
  • Le deuxième volet de l'ouvrage de Chambers " Logiciel pour l'analyse des données "qui est beaucoup plus récent et a un aspect beaucoup plus centré sur R - et deux chapitres sur l'extension de R. Le C et le C++ sont mentionnés. De plus, John me met en pièces pour ce que j'ai fait avec résumé donc rien que ça vaut le prix de l'entrée.

Cela dit, John aime de plus en plus Rcpp (et de contribuer) alors qu'il trouve la correspondance entre les objets R et les objets C++ (via Rcpp ) pour être très naturel -- et les ReferenceClasses y aident.

Edit 2 : Avec la question recentrée de Hadley, je très fortement Je vous conseille vivement d'envisager le C++. Il y a tellement de bêtises à faire avec le C c'est très fastidieux et difficile à comprendre. très évitable . Jetez un coup d'œil à la Vignette Rcpp-introduction . Un autre exemple simple est cet article de blog où je montre qu'au lieu de s'inquiéter des différences de 10% (dans l'un des exemples de Radford Neal), nous pouvons obtenir quatre-vingts fois augmente avec le C++ (sur ce qui est bien sûr un exemple artificiel).

Edit 3 : La complexité réside dans le fait que vous pouvez rencontrer des erreurs C++ qui sont, pour le moins, difficiles à comprendre. Mais pour utiliser Rcpp plutôt que de l'étendre, vous ne devriez pratiquement jamais en avoir besoin. Et bien que cette coût est indéniable, elle est largement éclipsée par la avantage de code plus simple, moins d'expressions passe-partout, pas de PROTECT/UNPROTECT, pas de gestion de la mémoire, etc. Doug Bates a déclaré hier qu'il trouvait que C++ et Rcpp ressemblaient beaucoup plus à l'écriture de R qu'à celle de C++. YMMV et tout ça.

54voto

Romain Francois Points 8223

Hadley,

Il est tout à fait possible d'écrire du code C++ qui soit similaire au code C.

Je comprends ce que vous dites sur le fait que le C++ est plus compliqué que le C. C'est le cas si vous voulez tout maîtriser : objets, templates, STL, méta-programmation par template, etc ... la plupart des gens n'ont pas besoin de ces choses et peuvent simplement compter sur les autres pour cela. L'implémentation de Rcpp est très compliquée, mais ce n'est pas parce que vous ne savez pas comment fonctionne votre réfrigérateur que vous ne pouvez pas ouvrir la porte et prendre du lait frais ...

D'après vos nombreuses contributions à R, ce qui me frappe, c'est que vous trouvez R quelque peu fastidieux (manipulation de données, graphiques, manipulation de chaînes de caractères, etc...). Eh bien préparez-vous à de nombreuses autres surprises avec l'API C interne de R. C'est très fastidieux.

De temps en temps, je lis les manuels R-exts ou R-ints. Cela m'aide. Mais la plupart du temps, quand je veux vraiment me renseigner sur quelque chose, je vais dans les sources de R, et aussi dans les sources des paquets écrits par exemple par Simon (il y a généralement beaucoup à apprendre là).

Rcpp est conçu pour faire disparaître ces aspects fastidieux de l'API.

Vous pouvez juger par vous-même ce que vous trouvez plus compliqué, obscur, etc ... en vous basant sur quelques exemples. Cette fonction crée un vecteur de caractères en utilisant 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);
}

En utilisant Rcpp, vous pouvez écrire la même fonction comme :

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

ou :

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

Comme Dirk l'a dit, il y a d'autres exemples sur les différentes vignettes. Nous orientons aussi généralement les gens vers nos tests unitaires, car chacun d'entre eux teste une partie très spécifique du code et est assez explicite.

Je suis évidemment partial ici, mais je recommanderais de se familiariser avec Rcpp au lieu d'apprendre l'API C de R, puis de venir sur la liste de diffusion si quelque chose n'est pas clair ou ne semble pas faisable avec Rcpp.

Bref, fin du discours commercial.

Je suppose que tout dépend du type de code que vous voulez écrire à terme.

Romain

28voto

Romain Francois Points 8223

@hadley : malheureusement, je n'ai pas de ressources spécifiques en tête pour vous aider à démarrer en C++. Je l'ai appris dans les livres de Scott Meyers (Effective C++, More effective C++, etc ...) mais ce ne sont pas vraiment ce qu'on pourrait appeler des introductions.

Nous utilisons presque exclusivement l'interface .Call pour appeler du code C++. La règle est assez simple :

  • La fonction C++ doit retourner un objet R. Tous les objets R sont SEXP.
  • La fonction C++ prend entre 0 et 65 objets R en entrée (à nouveau SEXP).
  • il doit (pas vraiment, mais nous pouvons garder cela pour plus tard) être déclaré avec un lien C, soit avec extern "C" o el RcppExport que Rcpp définit.

Ainsi, une fonction .Call est déclarée comme ceci dans un fichier d'en-tête :

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

et implémenté comme ceci dans un fichier .cpp :

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

Il n'y a pas grand-chose de plus à savoir sur l'API R pour utiliser Rcpp.

La plupart des gens ne veulent traiter que des vecteurs numériques dans Rcpp. Vous faites cela avec la classe NumericVector. Il existe plusieurs façons de créer un vecteur numérique :

A partir d'un objet existant que vous transmettez de R :

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

avec des valeurs données en utilisant la fonction statique ::create :

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

D'une taille donnée :

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

Ensuite, une fois que vous avez un vecteur, la chose la plus utile est d'en extraire un élément. Cela se fait à l'aide de l'opérateur[], avec une indexation basée sur 0. Ainsi, par exemple, l'addition des valeurs d'un vecteur numérique se fait comme suit :

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

Mais avec le sucre Rcpp, nous pouvons faire cela de manière beaucoup plus agréable maintenant :

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

Comme je l'ai déjà dit, tout dépend du type de code que vous voulez écrire. Regardez ce que les gens font dans les paquets qui reposent sur Rcpp, consultez les vignettes, les tests unitaires, revenez nous voir sur la liste de diffusion. Nous sommes toujours heureux de vous aider.

19voto

Romain Francois Points 8223

@jbremnant : C'est vrai. Les classes Rcpp implémentent quelque chose de proche du modèle RAII. Lorsqu'un objet Rcpp est créé, le constructeur prend les mesures appropriées pour s'assurer que l'objet R sous-jacent (SEXP) est protégé du ramasseur d'ordures. Le destructeur retire la protection. Ceci est expliqué dans la section Rcpp-intrduction vignette. L'implémentation sous-jacente s'appuie sur les fonctions de l'API R R_PreserveObject y R_ReleaseObject

Il y a effectivement une pénalité de performance due à l'encapsulation C++. Nous essayons de la maintenir au minimum avec l'inlining, etc... La pénalité est faible, et lorsque vous prenez en compte le gain en termes de temps d'écriture et de maintenance du code, elle n'est pas si importante.

L'appel de fonctions R à partir de la classe Rcpp Function est plus lent que l'appel direct de eval avec l'api C. Cela s'explique par le fait que nous prenons des précautions et enveloppons l'appel de fonction dans un bloc tryCatch afin de capturer les erreurs R et de les transformer en exceptions C++ pour qu'elles puissent être traitées à l'aide des try/catch standard en C++.

La plupart des gens veulent utiliser des vecteurs (en particulier NumericVector), et la pénalité est très faible avec cette classe. Le répertoire examples/ConvolveBenchmarks contient plusieurs variantes de la fameuse fonction convolution de R-exts et la vignette a des résultats de benchmark. Il s'avère que Rcpp la rend plus rapide que le code de référence qui utilise l'API R.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X