Каковы основные различия между этими двумя?
Классы *Vector
и *Matrix
в Rcpp действуют как оболочки для представления R * SEXP , например, S выражение, которое является указателем на данные. Подробности см. В Разделе 1.1 SEXP из R Internals . Rcpp . Это позволяет использовать конструкцию C ++ объектов из классов, которые включают указатель на данные. Это продвигает две ключевые функции:
- Плавный перенос между R и C ++ объектами и
- Низкая стоимость передачи между 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()
, требует тип возвращаемого значения, поскольку она передается при копировании и, следовательно, не изменяет существующее хранилище без вызова переназначения.
Чтобы подчеркнуть:
- Вектор
x
будет передан в C ++ по ссылке, например, &
в конце arma::vec&
или arma::ivec&
и
- Класс
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
Примечание: Ссылочный объект в ~ 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