¿Dónde puedo aprender a escribir código C para acelerar las funciones R lentas? [cerrado]

115

¿Cuál es el mejor recurso para aprender a escribir código C para usar con R? Conozco la sección de interfaces del sistema y de idiomas extranjeros de las extensiones R, pero me resulta bastante difícil. ¿Cuáles son los buenos recursos (tanto en línea como fuera de línea) para escribir código C para usar con R?

Para aclarar, no quiero aprender a escribir código C, quiero aprender cómo integrar mejor R y C. Por ejemplo, ¿cómo convierto de un vector entero C a un vector entero R (o viceversa) o de un escalar C a un vector R?

Hadley
fuente

Respuestas:

71

Bueno, ahí está el buen viejo ¡ Usa la fuente, Luke! --- R tiene mucho código C (muy eficiente) que uno puede estudiar, y CRAN tiene cientos de paquetes, algunos de autores de su confianza. Eso proporciona ejemplos reales y probados para estudiar y adaptar.

Pero como sospechaba Josh, me inclino más hacia C ++ y, por lo tanto, Rcpp . También tiene muchos ejemplos.

Editar: Hubo dos libros que encontré útiles:

  • La primera es la " Programación S " de Venables y Ripley, a pesar de que se está volviendo larga en el diente (y ha habido rumores de una segunda edición durante años). En ese momento simplemente no había nada más.
  • El segundo en " Software para análisis de datos " de Chambers, que es mucho más reciente y tiene una sensación centrada en R mucho más agradable, y dos capítulos sobre la extensión de R. Se mencionan tanto C como C ++. Además, John me destroza por lo que hice con el resumen, de modo que solo eso vale el precio de la entrada.

Dicho esto, John se está encariñando con Rcpp (y contribuyendo) ya que encuentra que la coincidencia entre los objetos R y los objetos C ++ (a través de Rcpp ) es muy natural, y ReferenceClasses ayuda allí.

Edición 2: con la pregunta reenfocada de Hadley, le recomiendo encarecidamente que considere C ++. Hay tantas tonterías estándar que tienes que hacer con C --- muy tediosas y muy evitables . Eche un vistazo a la viñeta de introducción de Rcpp . Otro ejemplo simple es esta publicación de blog donde muestro que en lugar de preocuparnos por las diferencias del 10% (en uno de los ejemplos de Radford Neal) podemos obtener aumentos de ochenta veces con C ++ (en lo que, por supuesto, es un ejemplo artificial).

Edición 3: Hay una complejidad en el hecho de que puede encontrarse con errores de C ++ que son, por decirlo suavemente, difíciles de asimilar. Pero para usar Rcpp en lugar de extenderlo, casi nunca debería necesitarlo. Y aunque este costo es innegable, queda eclipsado por el beneficio de un código más simple, menos repetitivo, sin PROTECCIÓN / DESPROTECCIÓN, sin administración de memoria, etc. pp. Doug Bates declaró ayer que encuentra que C ++ y Rcpp son mucho más como escribir R que escribir C ++. YMMV y todo eso.

Dirk Eddelbuettel
fuente
Esperaba obtener una respuesta "use Rcpp";) Sería realmente útil si pudiera explicar las desventajas de usar C ++ en lugar de C.Una de las principales parece ser que C ++ es mucho más complejo que C - esto hace que sea más difícil de usar? (O en la práctica, ¿puede escribir código C ++ que sea muy similar a C?) También agradecería más material de referencia dirigido a nuevos usuarios que no están familiarizados con la API de C existente.
hadley
2
Consulte Edición 3 y sí, puede . Meyers llama a C ++ un lenguaje de 'cuatro paradigmas' y no tiene que usar los cuatro. Usarlo como 'solo una mejor C' y usar Rcpp como pegamento para R está perfectamente bien. Nadie te
impone
@Dirk: gracias por la elaboración. Ya planteó la pregunta en nuestra oficina antes, ya que C se usa comúnmente aquí en lugar de C ++. ¿Cuándo sería beneficioso el uso de C sobre C ++, o simplemente dice "nunca C, siempre C ++"?
Joris Meys
Hadley: Genial. Estaríamos muy interesados ​​en sus comentarios. Únase a rcpp-devel y no se retenga. Sabemos que tenemos poca documentación, pero un par de ojos nuevos podría ayudar enormemente.
Dirk Eddelbuettel
6
@hadley, ¿eso significa que podríamos esperar algunas mejoras de velocidad ggplot?
aL3xa
56

Hadley,

Definitivamente puede escribir código C ++ que sea similar al código C.

Entiendo lo que dices acerca de que C ++ es más complicado que C. Esto es si quieres dominar todo: objetos, plantillas, STL, metaprogramación de plantillas, etc ... la mayoría de la gente no necesita estas cosas y puede confiar en otras. lo. La implementación de Rcpp es muy complicada, pero el hecho de que no sepas cómo funciona tu frigorífico no significa que no puedas abrir la puerta y coger leche fresca ...

De sus muchas contribuciones a R, lo que me sorprende es que encuentra R algo tedioso (manipulación de datos, gráficos, manipulación de cadenas, etc ...). Pues prepárate para muchas más sorpresas con la API C interna de R. Esto es muy tedioso.

De vez en cuando leo los manuales de R-exts o R-ints. Esto ayuda. Pero la mayoría de las veces, cuando realmente quiero saber algo, voy a la fuente R y también a la fuente de los paquetes escritos por, por ejemplo, Simon (generalmente hay mucho que aprender allí).

Rcpp está diseñado para hacer desaparecer estos tediosos aspectos de la API.

Puede juzgar por sí mismo lo que le resulte más complicado, ofuscado, etc., basándose en algunos ejemplos. Esta función crea un vector de caracteres utilizando la API de 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);
}

Usando Rcpp, puede escribir la misma función que:

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

o:

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

Como dijo Dirk, hay otros ejemplos en varias viñetas. También solemos señalar a las personas hacia nuestras pruebas unitarias porque cada una de ellas prueba una parte muy específica del código y se explican por sí mismas.

Obviamente, estoy sesgado aquí, pero recomendaría familiarizarse con Rcpp en lugar de aprender la API C de R, y luego ir a la lista de correo si algo no está claro o no parece factible con Rcpp.

De todos modos, fin del argumento de venta.

Supongo que todo depende del tipo de código que quieras escribir eventualmente.

Romain

Romain Francois
fuente
2
"Rcpp está diseñado para hacer desaparecer estos tediosos aspectos de la API" = exactamente lo que estoy buscando. ¡Gracias! Lo que sería realmente útil sería un manual de C ++ muy breve para alguien que esté familiarizado con C y quiera usar Rcpp.
hadley
bueno, ese breve ejemplo de Rcpp me vendió. Supongo que allocXX y UNPROTECT (1) se manejan de manera muy similar a cómo los punteros inteligentes administran el recurso. es decir, RAII. ¿Hay alguna penalización de rendimiento notable al usar Rcpp sobre la api vanilla C?
jbremnant
Abordamos eso en la introducción de Rcpp con un ejemplo de referencia (que también está en el paquete de fuentes / instalado). En resumen, sin penalización en absoluto.
Dirk Eddelbuettel
29

@hadley: desafortunadamente, no tengo recursos específicos en mente para ayudarlo a comenzar con C ++. Lo tomé de los libros de Scott Meyers (C ++ efectivo, C ++ más efectivo, etc.) pero estos no son realmente lo que uno podría llamar introductorios.

Usamos casi exclusivamente la interfaz .Call para llamar al código C ++. La regla es bastante fácil:

  • La función C ++ debe devolver un objeto R. Todos los objetos R son SEXP.
  • La función C ++ toma entre 0 y 65 objetos R como entrada (nuevamente SEXP)
  • debe (no realmente, pero podemos guardar para más tarde) ser declarado con C vinculación, ya sea con extern "C" o la RcppExport alias que define RCPP.

Entonces, una función .Call se declara así en algún archivo de encabezado:

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

e implementado así en un archivo .cpp:

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

No hay mucho más que saber sobre la API de R para usar Rcpp.

La mayoría de la gente solo quiere trabajar con vectores numéricos en Rcpp. Haz esto con la clase NumericVector. Hay varias formas de crear un vector numérico:

De un objeto existente que pasa de R:

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

Con valores dados usando la función :: crear estática:

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

De un tamaño dado:

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

Luego, una vez que tenga un vector, lo más útil es extraer un elemento de él. Esto se hace con el operador [], con indexación basada en 0, por lo que, por ejemplo, la suma de valores de un vector numérico es algo como esto:

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

Pero con el azúcar Rcpp podemos hacer esto mucho mejor ahora:

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

Como dije antes, todo depende del tipo de código que quieras escribir. Mire qué hace la gente en los paquetes que dependen de Rcpp, revise las viñetas, las pruebas unitarias, regrese a nosotros en la lista de correo. Siempre estamos felices de ayudar.

Romain Francois
fuente
20

@jbremnant: Eso es correcto. Las clases Rcpp implementan algo parecido al patrón RAII. Cuando se crea un objeto Rcpp, el constructor toma las medidas adecuadas para garantizar que el objeto R subyacente (SEXP) esté protegido del recolector de basura. El destructor retira la protección. Esto se explica en la viñeta Rcpp-intrducción . La implementación subyacente se basa en las funciones de R API R_PreserveObject y R_ReleaseObject

De hecho, existe una penalización de rendimiento debido a la encapsulación de C ++. Tratamos de mantener esto al mínimo con inlining, etc. La penalización es pequeña, y cuando se tiene en cuenta la ganancia en términos de tiempo que lleva escribir y mantener el código, no es tan relevante.

Llamar a funciones R desde la clase Rcpp Function es más lento que llamar directamente a eval con la api C. Esto se debe a que tomamos precauciones y ajustamos la llamada a la función en un bloque tryCatch para que capturemos los errores R y los promovamos a las excepciones de C ++ para que puedan tratarse usando el estándar try / catch en C ++.

La mayoría de la gente quiere usar vectores (especialmente NumericVector), y la penalización es muy pequeña con esta clase. El directorio examples / ConvolveBenchmarks contiene varias variantes de la notoria función de convolución de R-exts y la viñeta tiene resultados de referencia. Resulta que Rcpp lo hace más rápido que el código de referencia que usa la API R.

Romain Francois
fuente