2016-07-03 1 views
0

J'ai un programme qui calcule le produit de la matrice x'Ay à plusieurs reprises. Est-il préférable de calculer cela en faisant des appels à blas de MKL, c'est-à-dire cblas_dgemv et cblas_ddot, ce qui nécessite d'allouer de la mémoire à un vecteur temporaire, ou vaut mieux simplement prendre la somme de x_i * a_ij * y_j? En d'autres termes, les blas de MKL ajoutent-ils théoriquement de la valeur?Évitez les blas lorsque vous impliquez une allocation de mémoire temporaire?

J'ai comparé ceci pour mon ordinateur portable. Il n'y avait pratiquement aucune différence dans chacun des tests, sauf g ++ _ no_blas deux fois moins performant que les autres tests (pourquoi?). Il n'y avait pas non plus de différence entre O2, O3 et Ofast.

  1. g ++ _ blas_static 57ms
  2. g ++ _ blas_dynamic 58ms
  3. g ++ _ no_blas 100ms
  4. icpc_blas_static 57ms
  5. icpc_blas_dynamic 58ms
  6. icpc_no_blas 58ms

util .h

#ifndef UTIL_H 
#define UTIL_H 

#include <random> 
#include <memory> 
#include <iostream> 

struct rng 
{ 
     rng() : unif(0.0, 1.0) 
     { 
     } 

     std::default_random_engine re; 
     std::uniform_real_distribution<double> unif; 

     double rand_double() 
     { 
       return unif(re); 
     } 

     std::unique_ptr<double[]> generate_square_matrix(const unsigned N) 
     { 
       std::unique_ptr<double[]> p (new double[N * N]); 
       for (unsigned i = 0; i < N; ++i) 
       { 
         for (unsigned j = 0; j < N; ++j) 
         { 
           p.get()[i*N + j] = rand_double(); 
         } 
       } 
       return p; 
     } 

     std::unique_ptr<double[]> generate_vector(const unsigned N) 
     { 
       std::unique_ptr<double[]> p (new double[N]); 
       for (unsigned i = 0; i < N; ++i) 
       { 
         p.get()[i] = rand_double(); 
       } 
       return p; 
     } 
}; 

#endif // UTIL_H 

main.cpp

#include <iostream> 
#include <iomanip> 
#include <memory> 
#include <chrono> 
#include "util.h" 
#include "mkl.h" 

double vtmv_blas(double* x, double* A, double* y, const unsigned n) 
{ 
     double temp[n]; 
     cblas_dgemv(CblasRowMajor, CblasNoTrans, n, n, 1.0, A, n, y, 1, 0.0, temp, 1); 
     return cblas_ddot(n, temp, 1, x, 1); 
} 

double vtmv_non_blas(double* x, double* A, double* y, const unsigned n) 
{ 
     double r = 0; 
     for (unsigned i = 0; i < n; ++i) 
     { 
       for (unsigned j = 0; j < n; ++j) 
       { 
         r += x[i] * A[i*n + j] * y[j]; 
       } 
     } 
     return r; 
} 

int main() 
{ 
     std::cout << std::fixed; 
     std::cout << std::setprecision(2); 
     constexpr unsigned N = 10000; 
     rng r; 

     std::unique_ptr<double[]> A = r.generate_square_matrix(N); 
     std::unique_ptr<double[]> x = r.generate_vector(N); 
     std::unique_ptr<double[]> y = r.generate_vector(N); 

     auto start = std::chrono::system_clock::now(); 
     const double prod = vtmv_blas(x.get(), A.get(), y.get(), N); 
     auto end = std::chrono::system_clock::now(); 
     auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
       end - start); 

     std::cout << "Result: " << prod << std::endl; 
     std::cout << "Time (ms): " << duration.count() << std::endl; 

Répondre

0

GCC pas blas est pauvre parce qu'il n'utilise pas d'instructions vectorisées SMID, tandis que d'autres font tous. icpc s'auto-vectorise en boucle.

Vous ne montrez pas la taille de votre matrice, mais généralement gemv est lié à la mémoire. Comme la matrice est beaucoup plus grande qu'un vecteur de température, l'éliminer risque de ne pas être en mesure d'augmenter considérablement la performance.