Где я могу научиться писать код на C для ускорения медленных функций R? [закрыто]

115

Какой лучший ресурс для обучения написанию кода C для использования с R? Я знаю о разделе системных и языковых интерфейсов расширений R, но мне это довольно сложно. Какие есть хорошие ресурсы (как онлайн, так и офлайн) для написания кода C для использования с R?

Чтобы уточнить, я не хочу учиться писать код C, я хочу узнать, как лучше интегрировать R и C. Например, как мне преобразовать из целочисленного вектора C в целочисленный вектор R (или наоборот) или от скаляра C к вектору R?

Hadley
источник

Ответы:

71

Ну вот и старый добрый. Используй источник, Люк! --- Сам R имеет много (очень эффективного) кода C, который можно изучить, а в CRAN есть сотни пакетов, некоторые от авторов, которым вы доверяете. Это дает реальные проверенные примеры для изучения и адаптации.

Но, как и подозревал Джош, я больше склоняюсь к C ++ и, следовательно, к Rcpp . Там тоже много примеров.

Изменить: я нашел две полезные книги:

  • Первый - это « S-программирование » Венейблса и Рипли, хотя он и набирает обороты (слухи о втором издании ходили годами). В то время другого просто не было.
  • Вторая в « Программном обеспечении для анализа данных » Чемберса, которая появилась гораздо позже и имеет гораздо более приятное ощущение R-ориентированности, и две главы о расширении R. Упоминаются как C, так и C ++. Кроме того, Джон шлепает меня за то, что я сделал с дайджестом, так что одно это стоит цены входа.

Тем не менее, Джон все больше любит Rcpp (и вносит свой вклад), так как считает соответствие между объектами R и объектами C ++ (через Rcpp ) очень естественным - и в этом помогают ReferenceClasses.

Изменить 2: с перефокусированным вопросом Хэдли я очень настоятельно призываю вас рассмотреть C ++. Существует так много шаблонной ерунды, которую вы должны делать с C --- очень утомительно, и ее очень легко избежать . Взгляните на виньетку с введением Rcpp . Другой простой пример - это сообщение в блоге, где я показываю, что вместо того, чтобы беспокоиться о 10% различиях (в одном из примеров Рэдфорда Нила), мы можем получить восьмидесятикратное увеличение с помощью C ++ (это, конечно, надуманный пример).

Изменить 3: есть сложность в том, что вы можете столкнуться с ошибками C ++, которые, мягко говоря, трудно понять. Но чтобы просто использовать Rcpp, а не расширять его, он вам вряд ли когда-нибудь понадобится. И хотя эта стоимость неоспорима, она намного затмевается преимуществами более простого кода, меньшего количества шаблонов, отсутствия PROTECT / UNPROTECT, никакого управления памятью и т. Д. Дуг Бейтс только вчера заявил, что он считает, что C ++ и Rcpp намного больше похожи на написание R чем писать на C ++. YMMV и все такое.

Дирк Эддельбюттель
источник
Я ожидал, что получу ответ «используйте Rcpp»;) Было бы действительно полезно, если бы вы могли разъяснить недостатки использования C ++ вместо C. Один из основных, казалось бы, заключается в том, что C ++ намного сложнее, чем C - делает это затрудняет использование? (Или на практике вы можете написать код C ++, очень похожий на C?) Я также был бы признателен за дополнительные справочные материалы, предназначенные для новых пользователей, которые не знакомы с существующим C api.
Hadley 05
2
См. Edit 3, и да, вы можете . Мейерс называет C ++ языком «четырех парадигм», и вам не обязательно использовать все четыре. Использовать его как «просто лучший C» и использовать Rcpp как клей для R - это прекрасно. Никто не
навязывает
@Dirk: спасибо за разработку. Раньше это поднимало вопрос в нашем офисе, поскольку здесь обычно используется C вместо C ++. Когда будет выгодно использование C вместо C ++, или вы просто скажете «никогда не C, всегда C ++»?
Джорис Мейс
Хэдли: Круто. Нам будет очень интересно ваше мнение. Пожалуйста, присоединяйтесь к rcpp-devel и не сдерживайтесь. Мы знаем, что у нас короткая документация, но свежий взгляд может очень помочь.
Дирк Эддельбюттель
6
@hadley означает ли это, что мы можем ожидать улучшения скорости ggplot?
aL3xa
56

Хэдли,

Вы определенно можете написать код C ++, похожий на код C.

Я понимаю, что вы говорите о том, что C ++ более сложен, чем C. Это если вы хотите освоить все: объекты, шаблоны, STL, метапрограммирование шаблонов и т.д ... большинству людей эти вещи не нужны, и они могут просто полагаться на других к нему. Реализация Rcpp очень сложна, но то, что вы не знаете, как работает ваш холодильник, не означает, что вы не можете открыть дверь и взять свежее молоко ...

Из ваших многочисленных вкладов в R меня поразило то, что вы находите R несколько утомительным (манипулирование данными, графика, манипуляции со строками и т. Д.). Будьте готовы к еще большему количеству сюрпризов с внутренним C API R. Это очень утомительно.

Время от времени я читал руководства по R-exts или R-ints. Это помогает. Но в большинстве случаев, когда я действительно хочу что-то узнать, я обращаюсь к исходному тексту R, а также к источнику пакетов, написанному, например, Саймоном (обычно там есть чему поучиться).

Rcpp разработан, чтобы избавиться от этих утомительных аспектов API.

Вы можете сами судить, что вам кажется более сложным, запутанным и т. Д., На основе нескольких примеров. Эта функция создает вектор символов с помощью 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);
}

Используя Rcpp, вы можете написать такую ​​же функцию, как:

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

или:

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

Как сказал Дирк, в нескольких виньетках есть и другие примеры. Мы также обычно указываем людям на наши модульные тесты, потому что каждый из них тестирует очень конкретную часть кода и в некоторой степени не требует пояснений.

Я здесь явно предвзято, но я бы рекомендовал познакомиться с Rcpp вместо изучения C API R, а затем перейти к списку рассылки, если что-то неясно или не представляется возможным с Rcpp.

В любом случае, конец коммерческой презентации.

Я думаю, все зависит от того, какой код вы хотите написать в конечном итоге.

Ромен

Ромен Франсуа
источник
2
«Rcpp разработан, чтобы избавиться от этих утомительных аспектов API» = именно то, что я ищу. Спасибо! Что было бы действительно полезно, так это краткий учебник по C ++ для тех, кто знаком с C и хочет использовать Rcpp.
Хэдли 05
Хорошо, что этот короткий пример Rcpp меня продал. Я предполагаю, что allocXX и UNPROTECT (1) обрабатываются так же, как умные указатели управляют ресурсом. т.е. RAII. Есть ли заметное снижение производительности при использовании Rcpp вместо vanilla C api?
jbremnant 05
Мы обращаемся к этому во введении Rcpp с помощью тестового примера (который также находится в пакете sources / installed). Короче говоря, никакого штрафа.
Dirk Eddelbuettel
29

@hadley: к сожалению, у меня нет конкретных ресурсов, которые помогут вам начать работу с C ++. Я взял его из книг Скотта Мейерса («Эффективный С ++», «Более эффективный С ++» и т. Д.), Но это не совсем то, что можно было бы назвать вводным.

Мы почти исключительно используем интерфейс .Call для вызова кода C ++. Правило достаточно простое:

  • Функция C ++ должна возвращать объект R. Все объекты R - SEXP.
  • Функция C ++ принимает от 0 до 65 объектов R в качестве входных данных (снова SEXP).
  • он должен (не совсем, но мы можем сохранить это на потом) быть объявлен с компоновкой C, либо с extern «C», либо с псевдонимом RcppExport, который определяет Rcpp.

Итак, в каком-то заголовочном файле функция .Call объявляется следующим образом:

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

и реализован так в файле .cpp:

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

О том, что R API будет использовать Rcpp, мало что нужно.

Большинство людей хотят иметь дело только с числовыми векторами в Rcpp. Вы делаете это с помощью класса NumericVector. Есть несколько способов создать числовой вектор:

Из существующего объекта, который вы передаете от R:

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

С заданными значениями с использованием статической функции :: 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
 ) ;

Заданного размера:

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

Затем, когда у вас есть вектор, самое полезное - извлечь из него один элемент. Это делается с помощью оператора [] с индексированием на основе 0, поэтому, например, суммирование значений числового вектора происходит примерно так:

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

Но с сахаром Rcpp мы можем сделать это намного лучше:

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

Как я уже сказал, все зависит от того, какой код вы хотите написать. Посмотрите, что люди делают в пакетах, которые полагаются на Rcpp, проверьте виньетки, модульные тесты, вернитесь к нам в список рассылки. Мы всегда рады помочь.

Ромен Франсуа
источник
20

@jbremnant: Верно. Классы Rcpp реализуют что-то близкое к шаблону RAII. Когда создается объект Rcpp, конструктор принимает соответствующие меры для обеспечения защиты базового объекта R (SEXP) от сборщика мусора. Деструктор снимает защиту. Это объясняется в виньетке Rcpp-intrduction . Базовая реализация полагается на функции R API R_PreserveObject и R_ReleaseObject.

Действительно, из-за инкапсуляции C ++ наблюдается снижение производительности. Мы стараемся свести это к минимуму с помощью встраивания и т. Д. Штраф небольшой, и когда вы принимаете во внимание выигрыш с точки зрения времени, необходимого для написания и поддержки кода, это не так важно.

Вызов функций R из класса Function Rcpp выполняется медленнее, чем прямой вызов eval с помощью C api. Это связано с тем, что мы принимаем меры предосторожности и заключаем вызов функции в блок tryCatch, чтобы мы фиксировали ошибки R и продвигали их в исключения C ++, чтобы их можно было обрабатывать с помощью стандартного метода try / catch в C ++.

Большинство людей хотят использовать векторы (особенно NumericVector), и штраф за этот класс очень невелик. Каталог examples / ConvolveBenchmarks содержит несколько вариантов пресловутой функции свертки из R-exts, а виньетка содержит результаты тестов. Оказывается, Rcpp делает это быстрее, чем тестовый код, использующий R API.

Ромен Франсуа
источник