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

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

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

Ответ 1

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

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

Изменить: Были две полезные слова:

  • Первый из них - Venables и Ripley "S Programming", хотя он длится долго в зубе (а в течение многих лет ходят слухи о 2-м издании). В то время просто не было ничего.
  • Второе в "Программном обеспечении для анализа данных" в Chambers, которое намного более современно и имеет гораздо более приятное R-центрическое чувство - и две главы о расширении R. Оба C и С++ упоминаются. Плюс, Джон клонит меня за то, что я сделал с digest, так что один стоит цену за вход.

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

Отредактируйте 2: С вопросом, с которым связано Hadley, я очень настоятельно призываю вас рассмотреть С++. Существует так много глупостей, которые вы имеете с C - очень утомительным и очень избегаемым. Посмотрите на Rcpp-введение vignette. Еще один простой пример: этот пост в блоге, где я показываю, что вместо того, чтобы беспокоиться о 10% -ных различиях (в одном из примеров Radford Neal), мы можем получить восемьдесят увеличивается с С++ (на том, что, конечно, надуманный пример).

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

Ответ 2

Хэдли,

Вы можете определенно написать код на С++, аналогичный C-коду.

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

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

Время от времени я читал руководства 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.

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

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

Ромны

Ответ 3

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

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

  • Функция С++ должна возвращать объект R. Все объекты R являются SEXP.
  • Функция С++ принимает от 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, проверьте виньетки, модульные тесты, вернитесь к нам в список рассылки. Мы всегда рады помочь.

Ответ 4

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

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

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

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