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

115

Quelle est la meilleure ressource pour apprendre à écrire du code C à utiliser avec R? Je connais la section des interfaces système et en langue étrangère 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 un vecteur entier C en un vecteur entier R (ou vice versa) ou d'un scalaire C à un vecteur R?

hadley
la source

Réponses:

71

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 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 le soupçonnait, je penche davantage vers C ++ et donc Rcpp . Il contient également de nombreux exemples.

Edit: J'ai trouvé deux livres utiles:

  • Le premier est " S Programming " de Venables et Ripley, même si cela devient long dans la dent (et il y a eu des rumeurs d'une 2ème édition depuis des années). À l'époque, il n'y avait tout simplement rien d'autre.
  • Le deuxième du " Software for Data Analysis " de Chambers, qui est beaucoup plus récent et qui a une sensation bien plus agréable centrée sur R - et deux chapitres sur l'extension de R. C et C ++ sont mentionnés. De plus, John me déchire pour ce que j'ai fait avec le digest afin que cela vaut à lui seul 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 y contribue.

Edit 2: Avec la question recentrée de Hadley, je vous exhorte très fortement à considérer C ++. Il y a tellement d'absurdités passe-partout que vous avez à voir avec C --- très fastidieux et très évitable . Jetez un œil à la vignette d'introduction Rcpp . Un autre exemple simple est ce billet de blog où je montre qu'au lieu de nous soucier des différences de 10% (dans l'un des exemples de Radford Neal), nous pouvons obtenir des augmentations de quatre-vingt fois 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 à gérer. Mais pour simplement utiliser Rcpp plutôt que pour l'étendre, vous ne devriez presque jamais en avoir besoin. Et bien que ce coût soit indéniable, il est largement éclipsé par l' avantage d'un code plus simple, moins passe-partout, pas de PROTECT / UNPROTECT, pas de gestion de la mémoire, etc. que d'écrire du C ++. YMMV et tout ça.

Dirk Eddelbuettel
la source
Je m'attendais à avoir une réponse "utiliser Rcpp";) Ce serait vraiment utile si vous pouviez expliquer les inconvénients de l'utilisation de C ++ au lieu de C. Un des principaux semblerait être que C ++ est beaucoup plus complexe que C - cela rend-il plus difficile à utiliser? (Ou en pratique, pouvez-vous écrire du code C ++ très similaire à C?) J'apprécierais également plus de matériel de référence destiné aux nouveaux utilisateurs qui ne sont pas familiers avec l'API C existante.
hadley
2
Voir Edit 3 et oui, vous pouvez . Meyers appelle C ++ un langage à «quatre paradigmes» et vous n'êtes pas obligé d'utiliser les quatre. L'utiliser comme «juste un meilleur C» et utiliser Rcpp comme colle à R est parfaitement bien. Personne ne vous impose un style - ce n'est pas Java ;-)
Dirk Eddelbuettel
@Dirk: merci pour l'élaboration. Cela a soulevé la question dans notre bureau auparavant, car C est couramment utilisé ici au lieu de C ++. Quand l'utilisation de C sur C ++ serait-elle bénéfique, ou dites-vous simplement "jamais C, toujours C ++"?
Joris Meys
Hadley: Cool. Nous serions très intéressés par vos commentaires. Veuillez rejoindre rcpp-devel et ne vous retenez pas. Nous savons que nous sommes une documentation courte, mais un regard neuf pourrait grandement aider.
Dirk Eddelbuettel
6
@hadley cela signifie-t-il que nous pourrions nous attendre à des améliorations de vitesse ggplot?
aL3xa
56

Hadley,

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

Je comprends ce que vous dites sur le fait que C ++ est 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 à lui. La mise en œuvre 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 ...

De vos nombreuses contributions à R, ce qui me frappe, c'est que vous trouvez R un peu fastidieux (manipulation de données, graphisme, 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 exemple par Simon (il y a généralement beaucoup à apprendre).

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 ... en vous basant sur 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 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 biaisé ici, mais je recommanderais de se familiariser avec Rcpp au lieu d'apprendre l'API C de R, puis de venir à 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 voulez écrire éventuellement.

Romain

Romain François
la source
2
"Rcpp est conçu pour faire disparaître ces aspects fastidieux de l'API" = exactement ce que je recherche. Merci! Ce qui serait vraiment utile, ce serait une introduction à v. Brief C ++ pour quelqu'un qui est familier avec C et qui veut utiliser Rcpp.
hadley
gentil, ce court exemple de Rcpp m'a vendu. Je suppose que allocXX et UNPROTECT (1) sont traités de la même manière que les pointeurs intelligents gèrent la ressource. c'est-à-dire RAII. Y a-t-il une pénalité de performance notable en utilisant Rcpp sur l'api vanilla C?
jbremnant
Nous abordons cela dans l'introduction de Rcpp avec un exemple de référence (qui se trouve également dans le paquet sources / installé). Bref, pas de pénalité du tout.
Dirk Eddelbuettel
29

@hadley: malheureusement, je n'ai pas de ressources spécifiques en tête pour vous aider à démarrer sur C ++. Je l'ai pris dans les livres de Scott Meyers (C ++ efficace, C ++ plus efficace, etc ...) mais ce n'est 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 SEXP)
  • il doit (pas vraiment, mais nous pouvons sauver pour plus tard) être déclarée avec la liaison C, soit avec extern « C » ou l' RcppExport alias qui définit CRPP.

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 à 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 passez 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, le plus utile est d'en extraire un élément. Ceci est fait avec l'opérateur [], avec une indexation basée sur 0, donc par exemple la somme des valeurs d'un vecteur numérique va quelque chose comme ceci:

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 plus bien 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 font les gens dans les packages qui s'appuient sur Rcpp, vérifiez les vignettes, les tests unitaires, revenez nous voir sur la liste de diffusion. Nous sommes toujours heureux d'aider.

Romain François
la source
20

@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é du garbage collector. Le destructeur retire la protection. Ceci est expliqué dans la vignette Rcpp-intrduction . L'implémentation sous-jacente repose sur les fonctions R API R_PreserveObject et R_ReleaseObject

Il y a en effet une pénalité de performances due à l'encapsulation C ++. On essaie de garder cela au minimum avec l'inlining, etc ... La pénalité est faible, et quand on prend en compte le gain en termes de temps qu'il faut pour écrire et maintenir le 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. C'est parce 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 promouvoir en exceptions C ++ afin qu'elles puissent être traitées en utilisant le standard try / catch 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 fameuse fonction de convolution 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.

Romain François
la source