Где я могу узнать, как писать 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.