Gdzie mogę się nauczyć, jak pisać kod C, aby przyspieszyć wolne funkcje języka R? [Zamknięte]

115

Jaki jest najlepszy zasób do nauki pisania kodu C do użytku z R? Wiem o systemie i sekcji interfejsów języków obcych w rozszerzeniach R, ale uważam, że jest to dość trudne. Jakie są dobre zasoby (zarówno online, jak i offline) do pisania kodu C do użytku z R?

Aby wyjaśnić, nie chcę się uczyć, jak pisać kod w C, chcę się nauczyć, jak lepiej zintegrować R i C. Na przykład, jak przekonwertować z wektora liczb całkowitych C na wektor całkowity R (lub odwrotnie) czy ze skalara C do wektora R?

hadley
źródło

Odpowiedzi:

71

Cóż, jest stary dobry. Użyj źródła, Luke! --- Sam R ma mnóstwo (bardzo wydajnych) kodów C, które można przestudiować, a CRAN ma setki pakietów, niektóre od autorów, którym ufasz. To dostarcza prawdziwych, sprawdzonych przykładów do przestudiowania i dostosowania.

Ale jak podejrzewał Josh, skłaniam się bardziej w stronę C ++, a tym samym Rcpp . Zawiera również wiele przykładów.

Edycja: były dwie książki, które okazały się pomocne:

  • Pierwszym z nich jest " S Programming " Venablesa i Ripleya, mimo że jest już dość długi (a od lat krążą plotki o drugiej edycji). W tamtym czasie nie było po prostu nic innego.
  • Drugi w „ Oprogramowaniu do analizy danych ” Chambersa, który jest znacznie nowszy i ma o wiele ładniejszy wygląd R-centryczny - i dwa rozdziały o rozszerzaniu R. Wspomniano o C i C ++. Poza tym John niszczy mnie za to, co zrobiłem z trawieniem, więc sam jest wart ceny wstępu.

To powiedziawszy, John coraz bardziej lubi Rcpp (i wnosi wkład), ponieważ uważa, że ​​dopasowanie między obiektami R i obiektami C ++ (przez Rcpp ) jest bardzo naturalne - i pomaga w tym ReferenceClasses.

Edycja 2: Odnosząc się do pytania Hadleya, bardzo gorąco zachęcam do rozważenia C ++. Jest tyle schematycznych bzdur, że masz do czynienia z C - bardzo żmudne i łatwe do uniknięcia . Spójrz na winietę wprowadzającą Rcpp . Innym prostym przykładem jest ten post na blogu, w którym pokazuję, że zamiast martwić się o 10% różnic (w jednym z przykładów Radforda Neala), możemy uzyskać osiemdziesięciokrotny wzrost w C ++ (na co jest oczywiście wymyślonym przykładem).

Edycja 3: Jest złożoność polegająca na tym, że możesz napotkać błędy C ++, które są, delikatnie mówiąc, trudne do zrozumienia. Ale aby po prostu użyć Rcpp zamiast go rozszerzać, prawie nigdy nie powinieneś go potrzebować. I chociaż ten koszt jest niezaprzeczalny, jest on znacznie przyćmiony korzyścią wynikającą z prostszego kodu, mniej schematu, braku ochrony / ochrony, braku zarządzania pamięcią itp. Doug Bates wczoraj stwierdził, że uważa C ++ i Rcpp za znacznie bardziej podobne do pisania R niż pisanie w C ++. YMMV i tak dalej.

Dirk Eddelbuettel
źródło
Spodziewałem się, że otrzymam odpowiedź "użyj Rcpp";) Byłoby naprawdę przydatne, gdybyś mógł przeliterować wady używania C ++ zamiast C. Jedna z głównych wydaje się być taka, że ​​C ++ jest znacznie bardziej złożony niż C - robi to utrudnia korzystanie? (A może w praktyce możesz napisać kod w C ++, który jest bardzo podobny do C?) Byłbym również wdzięczny za więcej materiałów referencyjnych, które są przeznaczone dla nowych użytkowników, którzy nie są zaznajomieni z istniejącym interfejsem API w języku C.
hadley
2
Zobacz Edycja 3 i tak, możesz . Meyers nazywa C ++ językiem „czterech paradygmatów” i nie musisz używać wszystkich czterech. Używanie go jako „po prostu lepszego C” i użycie Rcpp jako kleju do R jest całkowicie w porządku. Nikt nie
narzuca
@Dirk: thx za opracowanie. Podniosło to już wcześniej pytanie w naszym biurze, ponieważ C jest tutaj powszechnie używane zamiast C ++. Kiedy użycie C zamiast C ++ byłoby korzystne, czy po prostu powiesz „nigdy C, zawsze C ++”?
Joris Meys
Hadley: Super. Będziemy bardzo zainteresowani Twoją opinią. Dołącz do rcpp-devel i nie wahaj się. Wiemy, że mamy krótką dokumentację - ale świeży zestaw oczu może bardzo pomóc.
Dirk Eddelbuettel
6
@hadley czy to oznacza, że ​​możemy spodziewać się poprawy szybkości ggplot?
aL3xa
56

Hadley,

Zdecydowanie możesz napisać kod C ++, który jest podobny do kodu C.

Rozumiem, co mówisz, że C ++ jest bardziej skomplikowany niż C.To jest, jeśli chcesz opanować wszystko: obiekty, szablony, STL, metaprogramowanie szablonów itp. ... większość ludzi nie potrzebuje tych rzeczy i może po prostu polegać na innych do tego. Wdrożenie Rcpp jest bardzo skomplikowane, ale to, że nie wiesz, jak działa Twoja lodówka, nie oznacza, że ​​nie możesz otworzyć drzwi i chwycić świeżego mleka ...

Z wielu twoich wkładów w R, uderza mnie to, że uważasz R za nieco nudne (manipulacja danymi, grafika, manipulowanie ciągami, itp ...). Przygotuj się na wiele innych niespodzianek dzięki wewnętrznemu C API R. Jest to bardzo żmudne.

Od czasu do czasu czytam instrukcje R-exts lub R-ints. To pomaga. Ale w większości przypadków, gdy naprawdę chcę się czegoś dowiedzieć, sięgam do źródeł R, a także do źródeł pakietów napisanych np. Przez Simona (zwykle jest tam wiele do nauczenia się).

Rcpp ma na celu wyeliminowanie tych żmudnych aspektów interfejsu API.

Możesz samodzielnie ocenić, co uważasz za bardziej skomplikowane, zaciemnione itp ... na podstawie kilku przykładów. Ta funkcja tworzy wektor znaków za pomocą C API:

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

Używając Rcpp, możesz napisać tę samą funkcję, co:

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

lub:

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

Jak powiedział Dirk, na kilku winietach są inne przykłady. Zwykle kierujemy również ludzi do naszych testów jednostkowych, ponieważ każdy z nich testuje bardzo określoną część kodu i nie wymaga wyjaśnień.

Jestem tutaj oczywiście stronniczy, ale poleciłbym zapoznać się z Rcpp zamiast uczyć się C API R, a następnie przejść do listy mailingowej, jeśli coś jest niejasne lub wydaje się nie do wykonania z Rcpp.

W każdym razie koniec prezentacji.

Myślę, że wszystko zależy od tego, jaki kod chcesz ostatecznie napisać.

Romain

Romain Francois
źródło
2
„Rcpp ma na celu wyeliminowanie tych żmudnych aspektów interfejsu API” = dokładnie to, czego szukam. Dzięki! Naprawdę przydatny byłby v. Krótki elementarz języka C ++ dla kogoś, kto zna C i chce używać Rcpp.
hadley
fajnie, ten krótki przykład Rcpp dał mi sprzedaż. Zakładam, że przydzielXX i UNPROTECT (1) są obsługiwane podobnie jak inteligentne wskaźniki zarządzające zasobem. tj. RAII. Czy istnieje znaczący spadek wydajności w przypadku używania Rcpp w porównaniu z interfejsem API vanilla C?
jbremnant
Zajmujemy się tym we wprowadzeniu do Rcpp na przykładzie testu porównawczego (który również znajduje się w źródłach / zainstalowanym pakiecie). Krótko mówiąc, żadnej kary.
Dirk Eddelbuettel
29

@hadley: niestety nie mam na myśli konkretnych zasobów, które pomogłyby w rozpoczęciu pracy z C ++. Wziąłem to z książek Scotta Meyersa (Efektywny C ++, Bardziej efektywny C ++ itd.), Ale nie są to tak naprawdę to, co można nazwać wprowadzeniem.

Do wywoływania kodu w C ++ używamy prawie wyłącznie interfejsu .Call. Zasada jest dość prosta:

  • Funkcja C ++ musi zwrócić obiekt R. Wszystkie obiekty R są SEXP.
  • Funkcja C ++ przyjmuje od 0 do 65 obiektów R jako dane wejściowe (ponownie SEXP)
  • musi (nie bardzo, ale możemy zapisać to na później) być zadeklarowana z wiązaniem C, albo z extern „C” lub RcppExport alias RCPP Definiuje.

Więc funkcja .Call zostaje zadeklarowana w taki sposób w jakimś pliku nagłówkowym:

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

i zaimplementowano w ten sposób w pliku .cpp:

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

Niewiele więcej trzeba wiedzieć o R API używającym Rcpp.

Większość ludzi chce mieć do czynienia tylko z wektorami numerycznymi w Rcpp. Robisz to za pomocą klasy NumericVector. Istnieje kilka sposobów tworzenia wektorów numerycznych:

Z istniejącego obiektu, który przekazujesz z R:

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

Z podanymi wartościami za pomocą funkcji :: 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
 ) ;

O podanym rozmiarze:

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

Kiedy już masz wektor, najbardziej użyteczną rzeczą jest wyodrębnienie z niego jednego elementu. Odbywa się to za pomocą operatora [], z indeksowaniem opartym na 0, więc na przykład sumowanie wartości wektora numerycznego wygląda mniej więcej tak:

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

Ale z cukrem Rcpp możemy teraz zrobić to znacznie przyjemniej:

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

Jak powiedziałem wcześniej, wszystko zależy od rodzaju kodu, który chcesz napisać. Zobacz, co ludzie robią w pakietach, które opierają się na Rcpp, sprawdź winiety, testy jednostkowe, wróć do nas na listę mailingową. Zawsze chętnie pomożemy.

Romain Francois
źródło
20

@jbremnant: Zgadza się. Klasy Rcpp implementują coś zbliżonego do wzorca RAII. Podczas tworzenia obiektu Rcpp konstruktor podejmuje odpowiednie środki, aby zapewnić, że bazowy obiekt R (SEXP) jest chroniony przed modułem odśmiecania pamięci. Destruktor cofa ochronę. Jest to wyjaśnione w winiecie Rcpp-intrduction . Podstawowa implementacja opiera się na funkcjach R API R_PreserveObject i R_ReleaseObject

W rzeczywistości istnieje spadek wydajności z powodu hermetyzacji C ++. Staramy się ograniczyć to do minimum, stosując wstawianie itp. Kara jest niewielka, a jeśli weźmiesz pod uwagę zysk w zakresie czasu potrzebnego na napisanie i utrzymanie kodu, nie jest to aż tak istotne.

Wywoływanie funkcji R z klasy Rcpp Function jest wolniejsze niż bezpośrednie wywoływanie funkcji eval za pomocą interfejsu API języka C. Dzieje się tak, ponieważ podejmujemy środki ostrożności i opakowujemy wywołanie funkcji w blok tryCatch, aby przechwytywać błędy języka R i promować je do wyjątków C ++, aby można było je rozwiązać za pomocą standardowego try / catch w C ++.

Większość ludzi chce używać wektorów (szczególnie NumericVector), a kara jest bardzo mała w przypadku tej klasy. Katalog examples / ConvolveBenchmarks zawiera kilka wariantów słynnej funkcji splotu z R-exts, a winieta ma wyniki wzorcowe. Okazuje się, że Rcpp sprawia, że ​​jest szybszy niż kod benchmarku korzystający z R API.

Romain Francois
źródło