web-dev-qa-db-fra.com

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 la section système et interfaces en langues étrangères des extensions R, mais je trouve cela 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 d'un vecteur entier C en un vecteur entier R (ou vice versa) ou d'un scalaire C à un vecteur R?

112
hadley

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

Mais comme Josh le soupçonnait, je penche plus vers le C++ et donc Rcpp . Il contient également de nombreux exemples.

Edit: J'ai trouvé deux livres utiles:

  • Le premier est Venables and Ripley's " S Programming " même si cela prend du temps (et il y a des rumeurs d'une 2e édition depuis des années ). À l'époque, il n'y avait tout simplement rien d'autre.
  • Le deuxième dans Chambers " Software for Data Analysis " qui est beaucoup plus récent et a une bien meilleure idée centrée sur R - et deux chapitres sur l'extension R. Les deux C et C++ sont mentionnés. De plus, John me déchire pour ce que j'ai fait avec digest afin que seul vaut le prix d'entrée.

Cela dit, John aime de plus en plus Rcpp (et contribue) car il trouve que la correspondance entre les objets R et les objets C++ (via Rcpp ) est très naturelle - et ReferenceClasses aider là-bas.

Edit 2: Avec la question recentrée de Hadley, je très fortement vous exhorte à considérez C++. Il y a tellement de bêtises que vous avez à faire avec C --- très fastidieux et très évitable . Jetez un œil à la Vignette d'introduction Rcpp . 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-vingt fois augmente avec C++ (sur ce qui est bien sûr un exemple artificiel).

Edit 3: Il y a de la complexité dans la mesure où vous pouvez rencontrer des erreurs C++ qui sont, pour le moins, difficiles à cerner. Mais pour simplement utiliser Rcpp plutôt que de l'étendre, vous ne devriez presque jamais en avoir besoin. Et bien que ce coût soit indéniable, il est de loin éclipsé par l'avantage de code plus simple, moins de passe-partout, pas de PROTECT/UNPROTECT, pas de gestion de la mémoire, etc. pp. Doug Bates a déclaré hier qu'il trouvait que C++ et Rcpp ressemblaient beaucoup plus à l'écriture de R qu'à l'écriture de C++. YMMV et tout ça.

68

Hadley,

Vous pouvez certainement écrire du code C++ similaire au code C.

Je comprends ce que vous dites à propos du C++ étant plus compliqué que C. C'est si vous voulez tout maîtriser: objets, modèles, STL, méta-programmation de modèles, etc ... la plupart des gens n'ont pas besoin de ces choses et peuvent simplement compter sur les autres à elle. La mise en œuvre de Rcpp est très compliquée, mais simplement parce que vous ne savez pas comment fonctionne votre réfrigérateur, cela ne signifie pas que vous ne pouvez pas ouvrir la porte et prendre du lait frais ...

De vos nombreuses contributions à R, ce qui me frappe, c'est que vous trouvez R un peu fastidieux (manipulation de données, graphiques, manipulation de chaînes, etc ...). Préparez-vous à bien d'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. CA aide. Mais la plupart du temps, quand je veux vraiment découvrir quelque chose, je vais dans la source R, et aussi dans la source des paquets écrits par ex. Simon (il y a généralement beaucoup à apprendre là-bas).

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

Vous pouvez juger par vous-même de ce que vous trouvez plus compliqué, obscurci, etc ... à partir de quelques exemples. Cette fonction crée un vecteur de caractères à l'aide de 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 que:

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

ou:

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

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

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

Bref, fin de l'argumentaire.

Je suppose que tout dépend du type de code que vous souhaitez éventuellement écrire.

Romain

54
Romain Francois

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

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

  • La fonction C++ doit renvoyer un objet R. Tous les objets R sont SEXP.
  • La fonction C++ prend entre 0 et 65 objets R en entrée (encore une fois SEXP)
  • il doit (pas vraiment, mais nous pouvons l'enregistrer pour plus tard) être déclaré avec la liaison C, soit avec extern "C" ou RcppExport l'alias 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:

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

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, le plus utile est d'en extraire un élément. Cela se fait avec l'opérateur [], avec une indexation basée sur 0, donc par exemple, la somme des valeurs d'un vecteur numérique se présente 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 beaucoup mieux 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 souhaitez écrire. Regardez ce que les gens font dans les packages qui dépendent de Rcpp, vérifiez les vignettes, les tests unitaires, revenez-nous sur la liste de diffusion. Nous sommes toujours heureux d'aider.

28
Romain Francois

@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 garantir que l'objet R sous-jacent (SEXP) est protégé contre le garbage collector. Le destructeur retire la protection. Ceci est expliqué dans la vignette Rcpp-intrduction . L'implémentation sous-jacente repose sur les fonctions de l'API R R_PreserveObject et R_ReleaseObject

Il y a en effet une pénalité de performance due à l'encapsulation C++. Nous essayons de garder cela au minimum avec l'inline, etc ... La pénalité est petite, et quand on prend en compte le gain de temps nécessaire pour écrire et maintenir du code, ce n'est pas si pertinent.

L'appel de fonctions R à partir de la classe Rcpp Function est plus lent que d'appeler directement eval avec l'api C. En effet, nous prenons des précautions et encapsulons l'appel de fonction dans un bloc tryCatch afin de capturer les erreurs R et de les promouvoir en exceptions C++ afin qu'elles puissent être traitées à l'aide de la tentative/capture standard en C++.

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

19
Romain Francois