Onde posso aprender a escrever código C para acelerar funções R lentas? [fechadas]


115

Qual é o melhor recurso para aprender a escrever código C para usar com R? Eu sei sobre a seção de interfaces de sistema e de idioma estrangeiro das extensões R, mas acho muito difícil ir. Quais são os bons recursos (online e offline) para escrever código C para uso com R?

Para esclarecer, não quero aprender a escrever código C, quero aprender a integrar melhor R e C. Por exemplo, como faço para converter de um vetor inteiro C para um vetor inteiro R (ou vice-versa) ou de um escalar C para um vetor R?

Respostas:


71

Bem, aí está o bom e velho Use a fonte, Luke! --- O próprio R tem muitos códigos C (muito eficientes) que podem ser estudados, e o CRAN tem centenas de pacotes, alguns de autores em quem você confia. Isso fornece exemplos reais e testados para estudar e adaptar.

Mas, como Josh suspeitou, eu me inclino mais para C ++ e, portanto, Rcpp . Também tem muitos exemplos.

Edit: Há dois livros que achei úteis:

  • O primeiro é Venables e Ripley " S Programming ", embora esteja ficando muito tempo (e há rumores de uma 2ª edição há anos). Na época, simplesmente não havia mais nada.
  • O segundo no " Software for Data Analysis " de Chambers, que é muito mais recente e tem uma sensação centrada em R muito mais agradável - e dois capítulos sobre como estender R. C e C ++ são mencionados. Além disso, John me destrói pelo que fiz com o digest , só isso já vale o preço da admissão.

Dito isso, John está cada vez mais interessado em Rcpp (e contribuindo), pois ele acha que a correspondência entre objetos R e objetos C ++ (via Rcpp ) é muito natural - e ReferenceClasses ajuda nisso .

Edição 2: Com a questão reformulada de Hadley, recomendo veementemente que você considere o C ++. Há tantas bobagens clandestinas que você tem a ver com C --- muito tedioso e muito evitável . Dê uma olhada na vinheta de introdução do Rcpp . Outro exemplo simples é este post de blog onde mostro que, em vez de nos preocupar com diferenças de 10% (em um dos exemplos de Radford Neal), podemos obter aumentos de oitenta vezes com C ++ (no que é, obviamente, um exemplo artificial).

Edição 3: Há complexidade em que você pode encontrar erros C ++ que são, para dizer o mínimo, difíceis de entender. Mas para usar apenas o Rcpp em vez de estendê-lo, você dificilmente precisará dele. E, embora esse custo seja inegável, ele é muito ofuscado pelo benefício de um código mais simples, menos clichê, sem PROTEGER / DESPROTEGER, sem gerenciamento de memória, etc. do que escrever C ++. YMMV e tudo isso.


Eu esperava receber uma resposta "usar Rcpp";) Seria realmente útil se você pudesse explicar as desvantagens de usar C ++ em vez de C. Uma das principais seria que C ++ é muito mais complexo que C isso torna mais difícil de usar? (Ou, na prática, você pode escrever código C ++ muito semelhante ao C?) Eu também gostaria de mais material de referência voltado para novos usuários que não estão familiarizados com a API C existente.
hadley

2
Veja a edição 3 e sim, você pode . Meyers chama C ++ de uma linguagem de 'quatro paradigmas' e você não precisa usar todos os quatro. Usá-lo como 'apenas um C melhor' e usar Rcpp como cola para R é perfeitamente adequado. Ninguém impõe um estilo a você - isso não é Java ;-)
Dirk Eddelbuettel

@Dirk: obrigado pela elaboração. Isso levantou a questão em nosso escritório antes, já que C é comumente usado aqui em vez de C ++. Quando o uso de C sobre C ++ seria benéfico ou você simplesmente diz "nunca C, sempre C ++"?
Joris Meys

Hadley: Legal. Estaríamos muito interessados ​​em seus comentários. Por favor, entre no rcpp-devel e não hesite. Sabemos que somos uma documentação curta - mas um novo par de olhos poderia ajudar tremendamente.
Dirk Eddelbuettel

6
@hadley isso significa que podemos esperar algumas melhorias de velocidade no ggplot?
aL3xa

56

Hadley,

Definitivamente, você pode escrever código C ++ semelhante ao código C.

Eu entendo o que você diz sobre C ++ ser mais complicado do que C. Isso se você quiser dominar tudo: objetos, modelos, STL, meta-programação de modelo, etc ... a maioria das pessoas não precisa dessas coisas e pode apenas confiar nos outros para isso. A implementação do Rcpp é muito complicada, mas só porque não sabe como funciona o seu frigorífico, não significa que não possa abrir a porta e apanhar leite fresco ...

De suas muitas contribuições para o R, o que me impressiona é que você acha o R um tanto entediante (manipulação de dados, gráficos, manipulação de strings, etc ...). Prepare-se para muitas mais surpresas com a API C interna de R. Isso é muito tedioso.

De vez em quando, leio os manuais R-exts ou R-ints. Isso ajuda. Mas na maioria das vezes, quando eu realmente quero descobrir algo, vou ao código-fonte do R e também ao código-fonte dos pacotes escritos por, por exemplo, Simon (geralmente há muito o que aprender lá).

O Rcpp foi projetado para eliminar esses aspectos tediosos da API.

Você pode julgar por si mesmo o que acha mais complicado, ofuscado, etc ... com base em alguns exemplos. Esta função cria um vetor de caracteres usando a 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);
}

Usando Rcpp, você pode escrever a mesma função que:

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

ou:

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

Como disse Dirk, há outros exemplos nas várias vinhetas. Também costumamos indicar às pessoas nossos testes de unidade, porque cada um deles testa uma parte muito específica do código e é um tanto autoexplicativo.

Obviamente, estou tendencioso aqui, mas eu recomendaria se familiarizar com o Rcpp em vez de aprender a API C do R e, em seguida, vir para a lista de e-mails se algo não estiver claro ou não parecer factível com o Rcpp.

Enfim, fim do discurso de vendas.

Acho que tudo depende do tipo de código que você deseja escrever eventualmente.

Romain


2
"Rcpp foi projetado para eliminar esses aspectos tediosos da API" = exatamente o que estou procurando. Obrigado! O que seria realmente útil seria uma cartilha v. Breve de C ++ para alguém que está familiarizado com C e deseja usar Rcpp.
hadley

legal, aquele pequeno exemplo de Rcpp me vendeu. Estou assumindo que o allocXX e o UNPROTECT (1) são tratados da mesma forma que os ponteiros inteligentes gerenciam o recurso. ou seja, RAII. Existe alguma penalidade de desempenho notável usando Rcpp em vez de vanilla C api?
jbremnant

Abordamos isso na introdução Rcpp com um exemplo de benchmark (que também está no pacote sources / installed). Resumindo, nenhuma penalidade.
Dirk Eddelbuettel

29

@hadley: infelizmente, não tenho recursos específicos em mente para ajudá-lo a começar a usar C ++. Peguei nos livros de Scott Meyers (Effective C ++, More Effective C ++, etc ...), mas estes não são realmente o que se poderia chamar de introdutórios.

Usamos quase exclusivamente a interface .Call para chamar o código C ++. A regra é bastante fácil:

  • A função C ++ deve retornar um objeto R. Todos os objetos R são SEXP.
  • A função C ++ leva entre 0 e 65 objetos R como entrada (novamente SEXP)
  • deve (não realmente, mas podemos guardar isso para mais tarde) ser declarado com C de ligação, quer com extern "C" ou o RcppExport alias que define Rcpp.

Portanto, uma função .Call é declarada assim em algum arquivo de cabeçalho:

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

e implementado assim em um arquivo .cpp:

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

Não há muito mais a saber sobre a API R para usar o Rcpp.

A maioria das pessoas deseja lidar apenas com vetores numéricos no Rcpp. Você faz isso com a classe NumericVector. Existem várias maneiras de criar um vetor numérico:

De um objeto existente que você passou de R:

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

Com os valores fornecidos usando a função :: criar 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 um determinado tamanho:

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

Então, uma vez que você tenha um vetor, a coisa mais útil é extrair um elemento dele. Isso é feito com o operador [], com indexação baseada em 0, então, por exemplo, somar valores de um vetor numérico é algo assim:

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

Mas com açúcar Rcpp podemos fazer isso muito mais bem agora:

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

Como eu disse antes, tudo depende de que tipo de código você deseja escrever. Veja o que as pessoas fazem em pacotes que dependem do Rcpp, verifique as vinhetas, os testes de unidade, volte para nós na lista de discussão. Nós estamos sempre felizes em ajudar.


20

@jbremnant: Isso mesmo. As classes Rcpp implementam algo próximo ao padrão RAII. Quando um objeto Rcpp é criado, o construtor toma as medidas apropriadas para garantir que o objeto R subjacente (SEXP) seja protegido do coletor de lixo. O destruidor retira a proteção. Isso é explicado na vinheta de introdução do Rcpp . A implementação subjacente depende das funções R API R_PreserveObject e R_ReleaseObject

De fato, há uma penalidade de desempenho devido ao encapsulamento C ++. Tentamos manter isso no mínimo com inlining, etc ... A penalidade é pequena, e quando você leva em consideração o ganho em termos de tempo que leva para escrever e manter o código, não é tão relevante.

Chamar funções R da classe Rcpp Function é mais lento do que chamar eval diretamente com a API C. Isso ocorre porque tomamos precauções e envolvemos a chamada de função em um bloco tryCatch para que capturemos os erros R e os promovamos a exceções C ++ para que possam ser tratados usando o padrão try / catch em C ++.

A maioria das pessoas deseja usar vetores (especialmente NumericVector), e a penalidade é muito pequena com essa classe. O diretório examples / ConvolveBenchmarks contém várias variantes da função de convolução notória de R-exts e a vinheta tem resultados de benchmark. Acontece que o Rcpp o torna mais rápido do que o código de benchmark que usa a API R.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.