Де я можу навчитися писати код C для прискорення повільних функцій R? [зачинено]


115

Який найкращий ресурс для того, щоб навчитися писати код C для використання з R? Я знаю про розділ розширень R для системних та іноземних мов , але мені здається, що це досить важко. Які хороші ресурси (як онлайн, так і офлайн) для написання коду С для використання з R?

Для уточнення, я не хочу навчитися писати код C, я хочу навчитися краще інтегрувати R і C. Наприклад, як перетворити з цілого вектора C в цілий вектор R (або навпаки) або від скаляра С до вектора R?

Відповіді:


71

Ну є старий добрий Користуйся джерелом, Лука! --- Сам R має безліч (дуже ефективний) код C, який можна вивчити, а CRAN - сотні пакунків, деякі з авторів, яким ви довіряєте. Це дає реальні перевірені приклади для вивчення та адаптації.

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

Редагувати: Було мені корисно дві книги:

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

Однак, Джон полюбляє Rcpp (і сприяє), оскільки вважає, що збіг між R-об’єктами та C ++-об'єктами (через Rcpp ) є дуже природним - і ReferenceClasses допомагають у цьому.

Редагувати 2: З переосмисленим питанням Хедлі я дуже наполегливо закликаю вас розглянути C ++. Стільки дурниць, котрих ти маєш до C - це дуже нудно і дуже уникнути . Погляньте на віньетку Rcpp-введення . Ще один простий приклад - це повідомлення в блозі, де я показую, що замість того, щоб турбуватися про 10% різниці (в одному з прикладів Radford Neal), ми можемо отримати вісімдесятирічне збільшення за допомогою C ++ (на що, звичайно, надуманий приклад).

Редагування 3: Є складність у тому, що ви можете зіткнутися з C ++ помилками, які, м'яко кажучи, важко піддатися. Але щоб просто використовувати Rcpp, а не розширювати його, навряд чи вам колись знадобиться. І хоча ця вартість є незаперечною, вона значно затьмарена користю від більш простого коду, менше котла, відсутності PROTECT / UNPROTECT, ніякого управління пам’яттю тощо. Дуг Бейтс лише вчора заявив, що вважає, що C ++ і Rcpp набагато більше схожі на написання R ніж писати на C ++. YMMV і все таке.


Я очікував, що отримаю відповідь "використання Rcpp";) Було б дуже корисно, якби ви могли прописати недоліки використання C ++ замість C. Одним з основних, здавалося б, є те, що C ++ набагато складніше, ніж C - це ускладнює використання? (Або на практиці, чи можете ви написати C ++-код, який дуже схожий на C?) Я також вдячний би більше довідкового матеріалу, який орієнтований на нових користувачів, які не знайомі з існуючим C api.
Хадлі

2
Дивіться Правка 3 і так, ви можете . Майєрс називає C ++ мовою "чотирьох парадигм", і вам не доведеться використовувати всі чотири. Використовувати його як «просто кращий C» та використовувати Rcpp як клей для R - це абсолютно добре. Ніхто не змушує на вас стилю - це не Java ;-)
Дірк Еддельбуеттель

@Dirk: thx для розробки. Раніше в нашій конторі було порушено питання, оскільки тут зазвичай використовується C замість C ++. Коли використання C над C ++ вигідно, чи ви просто говорите "ніколи не завжди, завжди C ++"?
Joris Meys

Хедлі: Класно. Ми були б дуже зацікавлені у ваших відгуках. Будь ласка, приєднуйтесь до rcpp-devel і не стримуйтесь. Ми знаємо, що ми маємо коротку документацію - але свіжий набір очей може допомогти надзвичайно.
Дірк Еддельбуеттель

6
@hadley це означає, що ми можемо очікувати певних поліпшень швидкості ggplot?
aL3xa

56

Хідлі,

Ви однозначно можете записати код C ++, подібний до коду С.

Я розумію, що ви говорите про те, що C ++ складніше, ніж C. Це якщо ви хочете освоїти все: об'єкти, шаблони, STL, мета-програмування шаблонів тощо ... більшості людей не потрібні ці речі і можуть просто покладатися на інших йому. Реалізація Rcpp дуже складна, але тільки тому, що ви не знаєте, як працює ваш холодильник, це не означає, що ви не можете відкрити двері та забрати свіже молоко ...

З багатьох ваших внесків в R, мене вражає те, що ви вважаєте, що R дещо втомлює (маніпулювання даними, графіка, стринг-маніпуляція тощо). Добре підготуйтеся до ще багатьох сюрпризів із внутрішнім API API R. Це дуже нудно.

Час від часу я читав посібники з R-exts або R-ints. Це допомагає. Але більшу частину часу, коли мені дуже хочеться дізнатися про щось, я заходжу в джерело R, а також у джерело пакунків, написаних, наприклад, Саймоном (там, як правило, багато чому навчитися).

Rcpp призначений для усунення цих виснажливих аспектів API.

Ви можете судити про те, що вам здається складнішим, затуманеним і т. Д. ... на основі кількох прикладів. Ця функція створює вектор символів за допомогою API C:

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 замість вивчення API API R, а потім перейдіть до списку розсилки, якщо щось не зрозуміло або не здається можливим з Rcpp.

У будь-якому випадку, кінець кроку продажу.

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

Ромен


2
"Rcpp призначений для того, щоб усунути ці нудні аспекти API" = саме те, що я шукаю. Дякую! Що було б дійсно корисним - це короткий буквар C ++ для того, хто знайомий з C та хоче використовувати Rcpp.
Хадлі

приємно, що цей короткий приклад Rcpp продав мене. Я припускаю, що allocXX і UNPROTECT (1) обробляються так само, як розумні покажчики управляють ресурсом. тобто RAII. Чи є якийсь помітний штраф за ефективність, використовуючи Rcpp над ванільним C api?
jbremnant

Ми звертаємося до цього в Rcpp-введенні з прикладом еталону (який також знаходиться в джерелі / встановленому пакеті). Словом, жодного штрафу.
Дірк Еддельбуеттель

29

@hadley: На жаль, я не маю на увазі конкретних ресурсів, щоб допомогти вам розпочати роботу на C ++. Я взяв це з книг Скотта Майєрса (Ефективні C ++, Ефективніший C ++ тощо), але це насправді не те, що можна назвати вступним.

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

  • Функція C ++ повинна повертати R-об'єкт. Усі об'єкти R - це SEXP.
  • Функція C ++ приймає між вхідними об'єктами від 0 до 65 R (знову SEXP)
  • він повинен (не дуже, але ми можемо зберегти це для пізніше) буде оголошено з C зв'язку, або з екстерном «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 static function:

 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-intruction . Основна реалізація покладається на функції R API R_PreserveObject і R_ReleaseObject

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

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.