Где я могу научиться писать код на C, чтобы ускорить медленные функции R?
Какой лучший ресурс для обучения написанию кода на C для использования с R? Я знаю о разделе системных и иностранных языков в расширениях R, но нахожу это довольно сложным делом. Каковы хорошие ресурсы (как онлайн, так и оффлайн) для написания кода на C для использования с R?
Чтобы уточнить, я не хочу учиться писать код на C, я хочу узнать, как лучше интегрировать R и C. Например, как я могу преобразовать из целочисленного вектора C в целочисленный вектор R (или наоборот) или из скаляра C в вектор R?
4 ответа
Ну, есть старый добрый Используйте источник, Люк! --- В самом 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 и все такое.
Hadley,
Вы определенно можете написать код на 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.
Во всяком случае, конец торгового предложения.
Я думаю, все зависит от того, какой код вы хотите написать в конце концов.
Ромен
@hadley: к сожалению, у меня нет конкретных ресурсов, чтобы помочь вам начать работу с C++. Я взял это из книг Скотта Мейерса (Эффективный C++, Более эффективный 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 Sugar мы можем сделать это намного лучше сейчас:
using namespace Rcpp ;
SEXP sum( SEXP x_ ){
NumericVector x(x_) ;
double res = sum( x ) ;
return wrap( res ) ;
}
Как я уже говорил, все зависит от того, какой код вы хотите написать. Посмотрите, что люди делают в пакетах, использующих Rcpp, проверьте виньетки, модульные тесты, вернитесь к нам в список рассылки. Мы всегда рады помочь.
@jbremnant: Это верно. Классы Rcpp реализуют нечто похожее на шаблон RAII. Когда объект Rcpp создан, конструктор принимает соответствующие меры для обеспечения защиты базового объекта R (SEXP) от сборщика мусора. Деструктор снимает защиту. Это объясняется в виньетке Rcpp-intrduction. Базовая реализация опирается на функции R API R_PreserveObject и R_ReleaseObject
Из-за инкапсуляции в C++ производительность действительно снижается. Мы стараемся свести это к минимуму с помощью встраивания и т. Д. Штраф невелик, и если принять во внимание выигрыш с точки зрения времени, необходимого для написания и поддержки кода, это не так уж важно.
Вызов функций R из класса Rcpp Function происходит медленнее, чем прямой вызов eval с помощью C api. Это происходит потому, что мы принимаем меры предосторожности и заключаем вызов функции в блок tryCatch, чтобы зафиксировать ошибки R и преобразовать их в исключения C++, чтобы их можно было обработать с помощью стандартного try/catch в C++.
Большинство людей хотят использовать векторы (особенно NumericVector), и штраф с этим классом очень мал. Каталог examples/ConvolveBenchmarks содержит несколько вариантов пресловутой функции свертки от R-exts, и виньетка имеет результаты тестов. Оказывается, что Rcpp делает это быстрее, чем тестовый код, который использует R API.