Выбор между NumericVector и arma :: vec в Rcpp - PullRequest
0 голосов
/ 04 мая 2018

С RcppArmadillo преобразование из R в Rcpp с arma::vec так же просто, как с Rcpp и NumericVector. Мой проект использует RcppArmadillo.

Я не уверен, что использовать, NumericVector или arma::vec? Каковы основные различия между этими двумя? Когда использовать что? Есть ли преимущество в производительности / памяти при использовании одного над другим? Единственная разница - функции-члены? И, в качестве дополнительного вопроса: я должен даже рассмотреть arma::colvec или arma::rowvec?

Ответы [ 2 ]

0 голосов
/ 05 мая 2018

Ответ @coatless верный, но он наполняет вас деталями, которые вы не просили.

В то же время ваш вопрос был недостаточно конкретизирован, поскольку вы не указали, для чего вам нужны векторы. С этой оговоркой я бы сказал, что

  • для простых случаев использования Rcpp в порядке, а RcppArmadillo - в равной степени
  • для случаев применения, требующих линейной алгебры, предпочитайте RcppArmadillo
  • производительность будет в значительной степени эквивалентна, с оговоркой, что вы хотите явный «вызов по ссылке» для RcppArmadillo, как указано выше
  • также существует большая разница между векторным доступом только для чтения (скажем, сокращение типа sum() или min() или поиск) и доступом для чтения и записи, где важно, как вы возвращаете модифицированный вектор
  • все варианты использования, как правило, намного быстрее, чем код R, поэтому в первом случае не зацикливайтесь на этом.

Как только вы все сделаете правильно, вы можете (и, возможно, должны) профиль.

0 голосов
/ 04 мая 2018

Каковы основные различия между этими двумя?

Классы *Vector и *Matrix в Rcpp действуют как оболочки для представления R * SEXP , например, S выражение, которое является указателем на данные. Подробности см. В Разделе 1.1 SEXP из R Internals . Rcpp . Это позволяет использовать конструкцию C ++ объектов из классов, которые включают указатель на данные. Это продвигает две ключевые функции:

  1. Плавный перенос между R и C ++ объектами и
  2. Низкая стоимость передачи между R и C ++ , поскольку передается только указатель.
    • , поскольку данные не скопированы , а ссылаются

Между тем, объекты arma похожи на традиционные std::vector<T> в том смысле, что копирование deep происходит между R и C ++ объектами. В этом утверждении есть одно исключение - наличие расширенного конструктора , который позволяет памяти за R объектом повторно использоваться внутри armadillo структура объекта. Таким образом, если вы не будете осторожны, вы можете понести ненужный штраф при переходе с R на C ++ и наоборот.

Примечание: Расширенный конструктор, позволяющий повторное использование памяти, не существует для arma::sp_mat. Таким образом, использование ссылок с разреженными матрицами, скорее всего, , а не даст желаемое ускорение при копировании с R до C ++ и обратно.

Вы можете просматривать различия, основанные главным образом на парадигме «передача по ссылке» или «передача по копии». Чтобы понять разницу вне кода, рассмотрим следующий GIF от mathwarehouse :

Чтобы проиллюстрировать этот сценарий в коде, рассмотрим следующие три функции:

#include <RcppArmadillo.h>

// [[Rcpp::depends(RcppArmadillo)]]

// [[Rcpp::export]]
void memory_reference_double_ex(arma::vec& x, double value) {
    x.fill(value);
}

// [[Rcpp::export]]
void memory_reference_int_ex(arma::ivec& x, int value) {
    x.fill(value);
}

// [[Rcpp::export]]
arma::vec memory_copy_ex(arma::vec x, int value) {
    x.fill(value);
    return x;
}

Обе функции memory_reference_double_ex() и memory_reference_int_ex() обновят объект внутри из R , при условии наличия соответствующего типа данных . В результате мы можем избежать возврата значения, указав void в их определениях, поскольку память, выделенная x, используется повторно . Третья функция, memory_copy_ex(), требует тип возвращаемого значения, поскольку она передается при копировании и, следовательно, не изменяет существующее хранилище без вызова переназначения.

Чтобы подчеркнуть:

  1. Вектор x будет передан в C ++ по ссылке, например, & в конце arma::vec& или arma::ivec& и
  2. Класс x в R равен либо double, либо integer, что означает, что мы сопоставляем базовый тип arma::vec, например, Col<double>, или arma::ivec, например Col<int>.

Давайте быстро рассмотрим два примера.

В первом примере мы рассмотрим результаты выполнения memory_reference_double_ex() и сравним его с результатами, сгенерированными memory_copy_ex(). Обратите внимание, что типы между возражениями, определенными в R и C ++ , являются одинаковыми (например, double). В следующем примере не удерживается.

x = c(0.1, 2.3, 4.8, 9.1)
typeof(x)
# [1] "double"

x
# [1] 0.1 2.3 4.8 9.1

# Nothing is returned...
memory_reference_double_ex(x, value = 9)

x
# [1] 9 9 9 9

a = memory_copy_ex(x, value = 3)

x
# [1] 9 9 9 9

a
#      [,1]
# [1,]    3
# [2,]    3
# [3,]    3
# [4,]    3

Теперь, что произойдет, если базовый тип объекта R будет integer вместо double?

x = c(1L, 2L, 3L, 4L)
typeof(x)
# [1] "integer"

x
# [1] 1 2 3 4

# Return nothing...
memory_reference_double_ex(x, value = 9)

x
# [1] 1 2 3 4

Что случилось? Почему x не обновился? Что ж, за кулисами Rcpp создал новое распределение памяти, которое было правильного типа - double, а не int - перед передачей его на armadillo. Это приводило к тому, что ссылочная «связь» между двумя объектами различалась.

Если мы перейдем к использованию целочисленного типа данных в векторе armadillo, обратите внимание, что теперь мы имеем тот же эффект, что и ранее:

memory_reference_int_ex(x, value = 3)

x
# [1] 3 3 3 3

Это приводит к дискуссии о полезности этих двух парадигм. Поскольку speed является предпочтительным тестом при работе с C ++ , давайте рассмотрим это с точки зрения теста.

Рассмотрим следующие две функции:

#include <RcppArmadillo.h>

// [[Rcpp::depends(RcppArmadillo)]]

// [[Rcpp::export]]
void copy_double_ex(arma::vec x, double value) {
    x.fill(value);
}

// [[Rcpp::export]]
void reference_double_ex(arma::vec& x, double value) {
    x.fill(value);
}

Запуск над ними микробенчмарка дает:

# install.packages("microbenchmark")
library("microbenchmark")

x = rep(1, 1e8)

micro_timings = microbenchmark(copy_double_ex(x, value = 9.0),
                               reference_double_ex(x, value = 9.0))
autoplot(micro_timings)
micro_timings

# Unit: milliseconds
#                               expr       min        lq      mean    median        uq      max neval
#       copy_double_ex(x, value = 9) 523.55708 529.23219 547.22669 536.71177 555.00069 640.5020   100
#  reference_double_ex(x, value = 9)  79.78624  80.70757  88.67695  82.44711  85.73199 308.4219   100

enter image description here

Примечание: Ссылочный объект в ~ 6,509771 раза быстрее за итерацию, чем скопированная парадигма, поскольку не приходится перераспределять и заполнять эту память.

Когда использовать что?

Что вам нужно , чтобы сделать?

Вы просто пытаетесь ускорить алгоритм, который основан на цикле, но не нуждается в строгих манипуляциях с линейной алгеброй?

Если это так, достаточно использовать Rcpp .

Вы пытаетесь выполнять линейные алгебраические манипуляции? Или вы надеетесь использовать этот код в нескольких библиотеках или вычислительных платформах (например, MATLAB, Python, R, ...)?

Если это так, вы должны написать суть алгоритма в armadillo и настроить соответствующие хуки для экспорта функций в R с помощью Rcpp .

Есть ли преимущество в производительности / памяти при использовании одного над другим?

Да, как указывалось ранее, определенно имеет преимущество в производительности / памяти. Кроме того, используя RcppArmadillo , вы фактически добавляете дополнительную библиотеку поверх Rcpp и, таким образом, увеличиваете общий объем установки, время компиляции и системные требования (см. Горе сборок macOS). Выясните , что требуется для вашего проекта, а затем выберите эту структуру.

Единственное отличие состоит в функциях-членах?

Не только функции-члены, но:

  • процедуры оценки в терминах матричной декомпозиции
  • вычисление значений статистической величины
  • генерация объекта
  • разреженное представление (избегайте манипуляций с объектом S4)

Это фундаментальные различия между Rcpp и броненосцем . Один предназначен для облегчения переноса R объектов в C ++ , тогда как другой предназначен для более строгих вычислений линейной алгебры. Это должно быть в значительной степени очевидно, поскольку Rcpp не не реализует какую-либо логику умножения матриц, тогда как armadillo использует системные подпрограммы базовой линейной алгебры (BLAS) для выполнения вычислений.

И, в качестве дополнительного вопроса: я должен даже рассмотреть arma :: colvec или arma :: rowvec?

Зависит от того, как вы хотите, чтобы результат был возвращен. Вы хотите иметь: 1 x N (вектор строки) или N x 1 (вектор столбца)? RcppArmadillo по умолчанию возвращает эти структуры как матрица объекты с соответствующими размерами и , а не традиционный 1D R вектор.

Как пример:

#include <RcppArmadillo.h>

// [[Rcpp::depends(RcppArmadillo)]]

// [[Rcpp::export]]
arma::vec col_example(int n) {
    arma::vec x = arma::randu<arma::vec>(n);
    return x;
}


// [[Rcpp::export]]
arma::rowvec row_example(int n) {
    arma::rowvec x = arma::randu<arma::rowvec>(n);
    return x;
}

Тест:

set.seed(1)
col_example(4)
#           [,1]
# [1,] 0.2655087
# [2,] 0.3721239
# [3,] 0.5728534
# [4,] 0.9082078

set.seed(1)
row_example(4)
#           [,1]      [,2]      [,3]      [,4]
# [1,] 0.2655087 0.3721239 0.5728534 0.9082078
...