Pour ajouter un autre point à la solution de Ralf Stubner, vous pouvez utiliser la version C++ suivante pour
- faire plusieurs colonnes en même temps pour éviter de relire le vecteur de sortie plusieurs fois.
-
ajouter __restrict__
pour potentiellement permettre des opérations vectorielles (probablement pas important ici car il s'agit seulement de lectures, je suppose).
include
using namespace Rcpp;
inline void mat_vec_mult_vanilla
(double const restrict m,
double const restrict v,
double restrict const res,
size_t const dn, size_t const dm) noexcept {
for(size_t j = 0; j < dm; ++j, ++v){
double r = res;
for(size_t i = 0; i < dn; ++i, ++r, ++m)
r += m v;
}
}
inline void mat_vec_mult
(double const restrict const m,
double const restrict const v,
double restrict const res,
size_t const dn, size_t const dm) noexcept {
size_t j(0L);
double const vj = v,
-
mi = m;
constexpr size_t const ncl(8L);
{
double const mvals[ncl];
size_t const end_j = dm - (dm % ncl),
inc = ncl dn;
for(; j < end_j; j += ncl, vj += ncl, mi += inc){
double r = res;
mvals[0] = mi;
for(size_t i = 1; i < ncl; ++i)
mvals[i] = mvals[i - 1L] + dn;
for(size_t i = 0; i < dn; ++i, ++r)
for(size_t ii = 0; ii < ncl; ++ii)
r += (vj + ii) *mvals[ii]++;
}
}
mat_vec_mult_vanilla(mi, vj, res, dn, dm - j);
}
// [[Rcpp::export("mat_vec_mult", rng = false)]]
NumericVector mat_vec_mult_cpp(NumericMatrix m, NumericVector v){
size_t const dn = m.nrow(),
dm = m.ncol();
NumericVector res(dn);
mat_vec_mult(&m[0], &v[0], &res[0], dn, dm);
return res;
}
// [[Rcpp::export("mat_vec_mult_vanilla", rng = false)]]
NumericVector mat_vec_mult_vanilla_cpp(NumericMatrix m, NumericVector v){
size_t const dn = m.nrow(),
dm = m.ncol();
NumericVector res(dn);
mat_vec_mult_vanilla(&m[0], &v[0], &res[0], dn, dm);
return res;
}
Le résultat avec -O3
dans mon fichier Makevars et gcc-8.3 est
set.seed(1)
dn <- 10001L
dm <- 10001L
m <- matrix(rnorm(dn * dm), dn, dm)
lv <- rnorm(dm)
all.equal(drop(m %*% lv), mat_vec_mult(m = m, v = lv))
#R> [1] TRUE
all.equal(drop(m %*% lv), mat_vec_mult_vanilla(m = m, v = lv))
#R> [1] TRUE
bench::mark(
R = m %*% lv,
`Version de l'OP` = my_mm(m = m, v = lv),
`BLAS` = blas_mm(m = m, v = lv),
`C++ vanilla` = mat_vec_mult_vanilla(m = m, v = lv),
`C++` = mat_vec_mult(m = m, v = lv), check = FALSE)
#R> # Un tibble: 5 x 13
#R> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
#R>
#R> 1 R 147.9ms 151ms 6.57 78.2KB 0 4 0 609ms
#R> 2 Version de l'OP 56.9ms 57.1ms 17.4 78.2KB 0 9 0 516ms
#R> 3 BLAS 90.1ms 90.7ms 11.0 78.2KB 0 6 0 545ms
#R> 4 C++ vanilla 57.2ms 57.4ms 17.4 78.2KB 0 9 0 518ms
#R> 5 C++ 51ms 51.4ms 19.3 78.2KB 0 10 0 519ms
Une légère amélioration. Le résultat peut toutefois dépendre beaucoup de la version de BLAS. La version que j'ai utilisée est
sessionInfo()
#R> #...
#R> Matrix products: default
#R> BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
#R> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1
#R> ...
Le fichier entier que j'ai Rcpp::sourceCpp()
é est
#include
#include
using namespace Rcpp;
inline void mat_vec_mult_vanilla
(double const * __restrict__ m,
double const * __restrict__ v,
double * __restrict__ const res,
size_t const dn, size_t const dm) noexcept {
for(size_t j = 0; j < dm; ++j, ++v){
double * r = res;
for(size_t i = 0; i < dn; ++i, ++r, ++m)
*r += *m * *v;
}
}
inline void mat_vec_mult
(double const * __restrict__ const m,
double const * __restrict__ const v,
double * __restrict__ const res,
size_t const dn, size_t const dm) noexcept {
size_t j(0L);
double const * vj = v,
* mi = m;
constexpr size_t const ncl(8L);
{
double const * mvals[ncl];
size_t const end_j = dm - (dm % ncl),
inc = ncl * dn;
for(; j < end_j; j += ncl, vj += ncl, mi += inc){
double *r = res;
mvals[0] = mi;
for(size_t i = 1; i < ncl; ++i)
mvals[i] = mvals[i - 1L] + dn;
for(size_t i = 0; i < dn; ++i, ++r)
for(size_t ii = 0; ii < ncl; ++ii)
*r += *(vj + ii) * *mvals[ii]++;
}
}
mat_vec_mult_vanilla(mi, vj, res, dn, dm - j);
}
// [[Rcpp::export("mat_vec_mult", rng = false)]]
NumericVector mat_vec_mult_cpp(NumericMatrix m, NumericVector v){
size_t const dn = m.nrow(),
dm = m.ncol();
NumericVector res(dn);
mat_vec_mult(&m[0], &v[0], &res[0], dn, dm);
return res;
}
// [[Rcpp::export("mat_vec_mult_vanilla", rng = false)]]
NumericVector mat_vec_mult_vanilla_cpp(NumericMatrix m, NumericVector v){
size_t const dn = m.nrow(),
dm = m.ncol();
NumericVector res(dn);
mat_vec_mult_vanilla(&m[0], &v[0], &res[0], dn, dm);
return res;
}
// [[Rcpp::export(rng = false)]]
NumericVector my_mm(NumericMatrix m, NumericVector v){
int nRow = m.rows();
int nCol = m.cols();
NumericVector ans(nRow);
double v_j;
for(int j = 0; j < nCol; j++){
v_j = v[j];
for(int i = 0; i < nRow; i++){
ans[i] += m(i,j) * v_j;
}
}
return(ans);
}
// [[Rcpp::export(rng = false)]]
NumericVector blas_mm(NumericMatrix m, NumericVector v){
int nRow = m.rows();
int nCol = m.cols();
NumericVector ans(nRow);
char trans = 'N';
double one = 1.0, zero = 0.0;
int ione = 1;
F77_CALL(dgemv)(&trans, &nRow, &nCol, &one, m.begin(), &nRow, v.begin(),
&ione, &zero, ans.begin(), &ione);
return ans;
}
/*** R
set.seed(1)
dn <- 10001L
dm <- 10001L
m <- matrix(rnorm(dn * dm), dn, dm)
lv <- rnorm(dm)
all.equal(drop(m %*% lv), mat_vec_mult(m = m, v = lv))
all.equal(drop(m %*% lv), mat_vec_mult_vanilla(m = m, v = lv))
bench::mark(
R = m %*% lv,
`Version de l'OP` = my_mm(m = m, v = lv),
`BLAS` = blas_mm(m = m, v = lv),
`C++ vanilla` = mat_vec_mult_vanilla(m = m, v = lv),
`C++` = mat_vec_mult(m = m, v = lv), check = FALSE)
*/
3 votes
Il se peut que cela ne tienne pas compte de tout, mais la méthode de R doit gérer les valeurs NA. De plus, d'après ce que je sais sur les méthodes numériques en informatique, il est probable que votre méthode naïve finisse par être inacceptablement inexacte dans certaines circonstances et que d'autres méthodes sacrifient un peu de vitesse pour une meilleure précision.
0 votes
En regardant:
getAnywhere(
%*%)
, nous avons:function (x, y) .Primitive("%*%")
. Donc, cela interagit avec une bibliothèque C mais comme le souligne @joran, vous ne tenez pas compte de la gestion desNA
.1 votes
@joran : autant que je sache, cela gère correctement les
NA
. La seule différence que je peux voir est que cela donne un vecteur et non une matrice.0 votes
Peut-être qu'il existe des optimisations de matrice x matrice qui ne sont pas particulièrement utiles lorsque l'une des dimensions est 1?
7 votes
Cette publication est ancienne et Radford a peut-être réussi à apporter quelques améliorations à R depuis qu'il l'a écrite, je pense que cela résume au moins le fait que la gestion de NA, Inf et NaN n'est pas toujours simple et nécessite un certain travail.
3 votes
Vous pouvez obtenir d'énormes améliorations en utilisant des bibliothèques d'algèbre linéaire pour les multiplications matricielles car elles gèrent mieux la mémoire et le cache. Pour les multiplications matrice-vecteur, les problèmes de mémoire sont moins importants, de sorte que l'optimisation est moindre. Voir par exemple ceci.