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

Другие вопросы по тегам