Использование библиотек С++ в пакете R

Каков наилучший способ использовать библиотеку С++ в R, надеясь сохранить структуры данных С++. Я вовсе не пользователь С++, поэтому я не совсем понимаю относительные достоинства доступных подходов. Руководство R-ext, похоже, предлагает обертывать каждую функцию С++ в C. Однако, по крайней мере, существует четыре или пять других способов включения С++.

Двумя способами являются пакеты с похожими линиями, Rcpp (поддерживаемые плодовитым переполнением Dirk Eddelbuettel) и пакеты RcppTemplate (как на CRAN), каковы различия между ними?

Другой пакет, rcppbind доступен, на R forge, который утверждает, что использует другой подход к связыванию С++ и R (я не знаю, что сказать).

Встроенный в CRAN пакет, утверждает, что разрешает встроенный C/С++. Я не уверен, что это отличается от встроенных функций, в том числе для обеспечения того, чтобы код был встроенным w/R.

И, наконец, RSwig, который выглядит в дикой природе, но неясно, насколько это поддерживается, поскольку авторская страница не обновлялась годами.

Мой вопрос в том, каковы относительные достоинства этих разных подходов. Какие из них самые портативные и надежные, которые проще всего реализовать. Если вы планируете распространять пакет на CRAN, какой из методов вы бы использовали?

Ответы

Ответ 1

Прежде всего, отказ от ответственности: я все время использую Rcpp. Фактически, когда (будучи переименованным по времени из Rcpp) RcppTemplate уже осиротел и без обновлений в течение двух лет, я начал поддерживать его под своим начальным именем Rcpp (в соответствии с которым он был внесен в RQuantLib). Это было около года назад, и я сделал пару дополнительных изменений, которые вы можете найти задокументированными в ChangeLog.

Теперь RcppTemplate совсем недавно вернулся через полные тридцать пять месяцев без каких-либо обновлений или исправлений. Он содержит интересный новый код, но кажется, что он не совместим с обратной связью, поэтому я не буду использовать его, где я уже использовал Rcpp.

Rcppbind не поддерживался очень активно, когда я проверял. У Whit Armstrong также есть шаблонный пакет интерфейса, называемый rabstraction.

Inline - это нечто совершенно другое: он упрощает цикл компиляции/ссылки путем "встраивания" вашей программы в строку символов R, которая затем скомпилируется, связывается и загружается. Я поговорил с Олегом о поддержке встроенной поддержки Rcpp, которая была бы приятной.

Swig тоже интересно. Джо Ван сделал отличную работу и завернул все QuantLib для R. Но когда я в последний раз его пробовал, он больше не работал из-за некоторых изменений внутри R. По словам кого-то из команды Swig, Джо все еще может работать над этим. В любом случае, цель Swig - большие библиотеки. Этот проект, вероятно, мог бы сделать с возрождением, но это не лишено технических проблем.

Еще одно упоминание следует перейти к RInside, который работает с Rcpp и позволяет вставлять R внутри приложений на С++.

Итак, чтобы подвести итог: Rcpp работает хорошо для меня, особенно для небольших исследовательских проектов, где вы просто хотите добавить функцию или два. Основное внимание уделяется простоте использования, и это позволяет вам "скрыть" некоторые из внутренних элементов R, с которыми не всегда приятно работать. Я знаю о многих других пользователях, которых я помогал и отправлял по электронной почте. Поэтому я бы сказал, пойти на это.

В учебниках "Введение в HPC с R" есть примеры Rcpp, RInside и inline.

Изменить: Итак, посмотрим на конкретный пример (взятый из слайдов "HPC с R Intro" и заимствованный у Стивена Милборроу, который взял его у Venables и Ripley). Задача состоит в перечислении всех возможных комбинаций определителя матрицы 2x2, содержащей только отдельные цифры в каждой позиции. Это можно сделать умными векторизованными способами (как мы обсудим в слайдах учебника) или грубой силой следующим образом:

#include <Rcpp.h>

RcppExport SEXP dd_rcpp(SEXP v) {
  SEXP  rl = R_NilValue;        // Use this when there is nothing to be returned.
  char* exceptionMesg = NULL;   // msg var in case of error

  try {
    RcppVector<int> vec(v);     // vec parameter viewed as vector of ints
    int n = vec.size(), i = 0;
    if (n != 10000) 
       throw std::length_error("Wrong vector size");
    for (int a = 0; a < 9; a++)
      for (int b = 0; b < 9; b++)
        for (int c = 0; c < 9; c++)
          for (int d = 0; d < 9; d++)
            vec(i++) = a*b - c*d;

    RcppResultSet rs;           // Build result set to be returned as list to R
    rs.add("vec", vec);         // vec as named element with name 'vec'
    rl = rs.getReturnList();    // Get the list to be returned to R.
  } catch(std::exception& ex) {
    exceptionMesg = copyMessageToR(ex.what());
  } catch(...) {
    exceptionMesg = copyMessageToR("unknown reason");
  }

  if (exceptionMesg != NULL) 
     Rf_error(exceptionMesg);

  return rl;
}

Если вы сохраните это как, скажем, dd.rcpp.cpp и установите Rcpp, просто используйте

PKG_CPPFLAGS=`Rscript -e 'Rcpp:::CxxFlags()'`  \
    PKG_LIBS=`Rscript -e 'Rcpp:::LdFlags()'`  \
    R CMD SHLIB dd.rcpp.cpp

для создания общей библиотеки. Мы используем Rscript (или r), чтобы спросить Rcpp о своих местах заголовка и библиотеки. После построения мы можем загрузить и использовать это из R следующим образом:

dyn.load("dd.rcpp.so")

dd.rcpp <- function() {
    x <- integer(10000)
    res <- .Call("dd_rcpp", x)
    tabulate(res$vec)
}

Точно так же вы можете отправлять векторы, матчи,... различных типов данных R и С++ обратно с легкостью. Надеюсь, что это поможет.

Изменить 2 (примерно через 5 лет):

Таким образом, этот ответ получил только верхнюю позицию и, следовательно, вырос в моей очереди. Прошло много времени с тех пор, как я написал его, и Rcpp получил намного более богатые возможности. Поэтому я очень быстро написал это

#include <Rcpp.h>

// [[Rcpp::export]]
Rcpp::IntegerVector dd2(Rcpp::IntegerVector vec) {
    int n = vec.size(), i = 0;
    if (n != 10000) 
        throw std::length_error("Wrong vector size");
    for (int a = 0; a < 9; a++)
        for (int b = 0; b < 9; b++)
            for (int c = 0; c < 9; c++)
                for (int d = 0; d < 9; d++)
                    vec(i++) = a*b - c*d;
    return vec;
}

/*** R
x <- integer(10000)
tabulate( dd2(x) )
*/

который можно использовать следующим образом с кодом в файле /tmp/dd.cpp

R> Rcpp::sourceCpp("/tmp/dd.cpp")    # on from any other file and path

R> x <- integer(10000)

R> tabulate( dd2(x) )
 [1]  87 132 105 155  93 158  91 161  72 104  45 147  41  96
[15]  72 120  36  90  32  87  67  42  26 120  41  36  27  75
[29]  20  62  16  69  19  28  49  45  12  18  11  57  14  48
[43]  10  18   7  12   6  46  23  10   4  10   4   6   3  38
[57]   2   4   2   3   2   2   1  17
R> 

Некоторые из ключевых отличий:

  • более простая сборка: просто sourceCpp() it; даже выполняет тестовый код R в конце
  • полнофункциональный IntegerVector тип
  • обертка для обработки исключений, автоматически добавленная генератором кода sourceCpp()