web-dev-qa-db-fra.com

Pourquoi MATLAB est-il si rapide dans la multiplication matricielle?

Je fais des tests avec CUDA, C++, C # et Java, et j'utilise MATLAB pour la vérification et la génération de matrices. Mais lorsque je me multiplie avec MATLAB, 2048x2048 Et des matrices encore plus grosses sont presque instantanément multipliées.

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

Seul CUDA est compétitif, mais je pensais qu'au moins C++ serait un peu proche et non pas 60x Plus lentement.

Ma question est donc la suivante: comment MATLAB fait-il cela si vite?

Code C++:

float temp = 0;
timer.start();
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * matice2[m][k];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

Edit: Je ne sais pas non plus quoi penser des résultats en C #. L'algorithme est identique à C++ et Java, mais il y a un saut géant 2048 À partir de 1024?

Edit2: Mise à jour des résultats MATLAB et 4096x4096

178
Wolf

Voici mes résultats en utilisant MATLAB R2011a + Parallel Computing Toolbox sur une machine avec un Tesla C2070:

>> A = Rand(1024); gA = gpuArray(A);
% warm up by executing the operations a couple of times, and then:
>> tic, C = A * A; toc
Elapsed time is 0.075396 seconds.
>> tic, gC = gA * gA; toc
Elapsed time is 0.008621 seconds.

MATLAB utilise des bibliothèques hautement optimisées pour la multiplication de matrice, raison pour laquelle la multiplication de matrice MATLAB simple est si rapide. La version gpuArray utilise MAGMA .

Mise à jour à l'aide de R2014a sur un ordinateur doté d'une Tesla K20c et des nouvelles fonctions timeit et gputimeit:

>> A = Rand(1024); gA = gpuArray(A);
>> timeit(@()A*A)
ans =
    0.0324
>> gputimeit(@()gA*gA)
ans =
    0.0022

Mise à jour à l'aide de R2018b sur une machine WIN64 avec 16 cœurs physiques et une Tesla V100:

>> timeit(@()A*A)
ans =
    0.0229
>> gputimeit(@()gA*gA)
ans =
   4.8019e-04
82
Edric

Ce type de question est récurrent et doit recevoir une réponse plus claire que "Matlab utilise des bibliothèques hautement optimisées" ou "Matlab utilise le MKL" pour une fois sur Stackoverflow.

Historique:

La multiplication matricielle (ainsi que la multiplication vecteur-matrice, la multiplication vecteur-vecteur et de nombreuses décompositions matricielles) est (sont) le problème le plus important en algèbre linéaire. Les ingénieurs ont résolu ces problèmes avec les ordinateurs depuis le début.

Je ne suis pas un expert en histoire, mais apparemment, à l'époque, tout le monde venait de réécrire sa version en Fortran avec de simples boucles. Une certaine normalisation est ensuite apparue, avec l'identification des "noyaux" (routines de base) que la plupart des problèmes d'algèbre linéaire nécessitaient pour être résolus. Ces opérations de base ont ensuite été normalisées dans une spécification appelée: Sous-programmes de base d’algèbre linéaire (BLAS). Les ingénieurs pouvaient ensuite appeler ces routines BLAS standard et bien testées dans leur code, ce qui simplifiait grandement leur travail.

BLAS:

BLAS a évolué du niveau 1 (la première version définissant les opérations vectorielles scalaires et vectorielles) au niveau 2 (opérations matricielles) au niveau 3 (opérations matricielles) et fournit de plus en plus de "noyaux" ainsi normalisés. et plus des opérations fondamentales d'algèbre linéaire. Les implémentations Fortran 77 d'origine sont toujours disponibles sur site Web de Netlib .

Vers de meilleures performances:

Ainsi, au fil des années (notamment entre les versions BLAS de niveaux 1 et 2: début des années 80), le matériel a changé, avec l'avènement des opérations vectorielles et des hiérarchies de cache. Ces évolutions ont permis d’augmenter considérablement les performances des sous-routines BLAS. Différents fournisseurs ont ensuite proposé des routines BLAS de plus en plus efficaces.

Je ne connais pas toutes les implémentations historiques (je n'étais ni né ni à l'époque), mais deux des plus remarquables sont apparues au début des années 2000: l'Intel MKL et GotoBLAS. Votre Matlab utilise le processeur Intel MKL, qui est un très bon BLAS optimisé, ce qui explique les performances exceptionnelles que vous voyez.

Détails techniques sur la multiplication de matrices:

Alors pourquoi Matlab (le MKL) est-il si rapide à dgemm (multiplication matrice-matrice générale double précision)? En termes simples: parce qu'il utilise la vectorisation et une bonne mise en cache des données. En termes plus complexes: voir le article fourni par Jonathan Moore.

En gros, lorsque vous effectuez votre multiplication dans le code C++ que vous avez fourni, vous n'êtes pas du tout compatible avec le cache. Puisque je soupçonne que vous avez créé un tableau de pointeurs sur les tableaux de rangées, vos accès dans votre boucle interne à la k-ème colonne de "matice2": matice2[m][k] sont très lents. En effet, lorsque vous accédez à matice2[0][k], vous devez obtenir le k-ième élément du tableau 0 de votre matrice. Ensuite, à la prochaine itération, vous devez accéder à matice2[1][k], qui est le k-ème élément d'un autre tableau (le tableau 1). Ensuite, à la prochaine itération, vous accédez à un autre tableau, et ainsi de suite ... Depuis la matrice entière matice2 ne peut pas rentrer dans les caches les plus hauts (c'est 8*1024*1024 _ octets), le programme doit extraire l’élément souhaité de la mémoire principale, ce qui lui fait perdre beaucoup de temps.

Si vous venez de transposer la matrice, de sorte que les accès se trouvent dans des adresses mémoire contiguës, votre code serait déjà beaucoup plus rapide, car le compilateur peut désormais charger des lignes entières dans le cache en même temps. Essayez juste cette version modifiée:

timer.start();
float temp = 0;
//transpose matice2
for (int p = 0; p < rozmer; p++)
{
    for (int q = 0; q < rozmer; q++)
    {
        tempmat[p][q] = matice2[q][p];
    }
}
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * tempmat[k][m];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

Ainsi, vous pouvez voir à quel point la localisation du cache a considérablement amélioré les performances de votre code. Maintenant, les vraies implémentations dgemm exploitent cela à un niveau très étendu: elles effectuent la multiplication sur des blocs de la matrice définis par la taille du TLB qu'ils transmettent au processeur exactement la quantité de données qu'il peut traiter. L’autre aspect est la vectorisation: ils utilisent les instructions vectorisées du processeur pour obtenir un débit d’instruction optimal, ce que vous ne pouvez pas vraiment faire à partir de votre code C++ multiplate-forme.

Enfin, les personnes affirmant que c'est à cause de l'algorithme de Strassen ou de Coppersmith – Winograd sont fausses, ces deux algorithmes ne sont pas implémentables dans la pratique, à cause des considérations matérielles mentionnées ci-dessus.

161
reverse_engineer

C'est pourquoi . MATLAB n'effectue pas une multiplication de matrice naïve en bouclant sur chaque élément comme vous l'avez fait dans votre code C++.

Bien sûr, je suppose que vous venez d'utiliser C=A*B au lieu d'écrire vous-même une fonction de multiplication.

39
Doug Stephen

Matlab a incorporé LAPACK il y a quelque temps, donc je suppose que leur multiplication matricielle utilise quelque chose au moins aussi rapide. Le code source et la documentation LAPACK sont facilement disponibles.

Vous pouvez également consulter l'article de Goto et Van De Geijn intitulé "Anatomie d'une multiplication matricielle haute performance" à l'adresse http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1. 1.140.1785 & rep = rep1 & type = pdf

19
Jonathan Moore

La réponse est LAPACK et BLAS Les bibliothèques rendent MATLAB aveuglément rapide aux opérations de la matrice, pas tout code propriétaire par les gens de MATLAB.

Utilisez les bibliothèques LAPACK et/ou BLAS dans votre code C++ pour les opérations de matrice et vous devriez obtenir des performances similaires à celles de MATLAB. Ces bibliothèques devraient être librement disponibles sur tout système moderne et des parties ont été développées au cours des décennies dans le monde universitaire. Notez qu'il existe plusieurs implémentations, y compris des sources fermées telles que Intel MKL .

Une discussion sur la manière dont BLAS obtient de hautes performances est disponible ici.


Au fait, mon expérience me fait beaucoup de peine d'appeler les bibliothèques LAPACK directement à partir de c (mais cela en vaut la peine). Vous devez lire la documentation TRÈS précisément.

9
Matthew Gunn

Lors de la multiplication de matrice, vous utilisez une méthode de multiplication naïve qui prend un temps de O(n^3).

Il existe un algorithme de multiplication de matrice qui prend O(n^2.4). Ce qui signifie qu'à n=2000 votre algorithme nécessite environ 100 fois plus de calculs que le meilleur algorithme.
Vous devriez vraiment vérifier la page de Wikipédia pour la multiplication de matrice pour plus d’informations sur les moyens efficaces de l’appliquer.

9
Jouni Osmala

Selon votre version de Matlab, je pense qu’il utilise peut-être déjà votre processeur graphique.

Autre chose; Matlab garde trace de nombreuses propriétés de votre matrice; soit sa diagonale, hermétienne, etc., et spécialise ses algorithmes basés sur ceux-ci. Peut-être sa spécialisation basée sur la matrice zéro que vous passez, ou quelque chose comme ça? Peut-être met-il en cache des appels de fonction répétés, ce qui perturbe vos timings? Peut-être optimise-t-il les produits matriciels inutilisés répétés?

Pour vous protéger contre de telles choses, utilisez une matrice de nombres aléatoires et assurez-vous de forcer l'exécution en imprimant le résultat sur un écran ou sur un disque ou à un autre.

6

MATLAB utilise une implémentation hautement optimisée de LAPACK d'Intel connue sous le nom Intel Math Kernel Library (Intel MKL) - plus précisément fonction dgemm . La vitesse Cette bibliothèque tire parti des fonctionnalités du processeur, notamment les instructions SIMD et les processeurs multicœurs. Ils ne documentent pas l'algorithme spécifique qu'ils utilisent. Si vous appelez Intel MKL à partir de C++, vous devriez voir des performances similaires.

Je ne suis pas sûr de ce que la bibliothèque MATLAB utilise pour la multiplication GPU mais probablement quelque chose comme nVidia CUBLAS .

3
gregswiss

Le contraste important n'est pas seulement dû à l'optimisation étonnante de Matlab (comme cela a déjà été mentionné dans de nombreuses autres réponses), mais également à la manière dont vous avez formulé la matrice en tant qu'objet.

On dirait que vous avez fait matrice une liste de listes? Une liste de listes contient des pointeurs sur des listes contenant ensuite vos éléments de matrice. Les emplacements des listes contenues sont attribués de manière arbitraire. Lorsque vous parcourez votre premier index (numéro de ligne?), Le temps d’accès à la mémoire est très important. En comparaison, pourquoi n'essayez-vous pas d'implémenter la matrice en tant que liste/vecteur unique en utilisant la méthode suivante?

#include <vector>

struct matrix {
    matrix(int x, int y) : n_row(x), n_col(y), M(x * y) {}
    int n_row;
    int n_col;
    std::vector<double> M;
    double &operator()(int i, int j);
};

Et

double &matrix::operator()(int i, int j) {
    return M[n_col * i + j];
}

Le même algorithme de multiplication doit être utilisé pour que le nombre de flop soit le même. (n ^ 3 pour les matrices carrées de taille n)

Je vous demande de chronométrer pour que le résultat soit comparable à ce que vous aviez auparavant (sur la même machine). Avec la comparaison, vous montrez exactement à quel point le temps d'accès à la mémoire peut être important!

2
Argyll

La réponse générale à la question "Pourquoi matlab est-il plus rapide à exécuter xxx que d’autres programmes" est que matlab possède de nombreuses fonctions intégrées optimisées.

Les autres programmes utilisés ne disposent souvent pas de ces fonctions, de sorte que les utilisateurs appliquent leurs propres solutions créatives, qui sont étonnamment plus lentes que du code optimisé par des professionnels.

Cela peut être interprété de deux manières:

1) La voie commune/théorique: Matlab n’est pas significativement plus rapide, vous ne faites que mal le repère

2) Le moyen réaliste: Matlab est plus rapide dans la pratique, car les langages comme c ++ sont trop facilement utilisés de manière inefficace.

2
Dennis Jaheruddin

C'est lent en C++ parce que vous n'utilisez pas le multithreading. Essentiellement, si A = BC, où elles sont toutes des matrices, la première ligne de A peut être calculée indépendamment de la 2ème ligne, etc. Si A, B et C sont toutes des matrices n sur n, vous pouvez accélérer la multiplication de un facteur de n ^ 2, comme

a_ {i, j} = somme_ {k} b_ {i, k} c_ {k, j}

Si vous utilisez, par exemple, Eigen [ http://eigen.tuxfamily.org/dox/GettingStarted.html ], le multithreading est intégré et le nombre de threads est ajustable.

2
wsw

Parce que MATLAB est un langage de programmation d'abord développé pour l'algèbre linéaire numérique (matrice manipulations), qui a des bibliothèques spécialement développées pour les multiplications matricielles. Et maintenant MATLAB peut également utiliser le GPU (unité de traitement graphique) ) pour cela en plus.

Et si nous regardons vos résultats de calcul:

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

alors nous pouvons voir que non seulement MATLAB est si rapide dans la multiplication matricielle: CUDA C (langage de programmation de NVIDIA) en a meilleurs résultats que MATLAB. CUDA C dispose également de bibliothèques spécialement développées pour les multiplications matricielles et utilise les GPU.

Bref historique de MATLAB

Cleve Moler, président du département d’informatique de l’Université du Nouveau-Mexique, a commencé à développer MATLAB à la fin des années 1970. Il l’a conçu pour donner à ses élèves l’accès à LINPACK (une bibliothèque de logiciels pour effectuant l'algèbre linéaire numérique) et EISPACK (est une bibliothèque logicielle pour le calcul numérique de l'algèbre linéaire) sans avoir à apprendre le Fortran. Il s'est rapidement répandu dans d'autres universités et a trouvé un large public au sein de la communauté des mathématiques appliquées. Jack Little, un ingénieur, a été mis au courant lors d'une visite de Moler à l'Université de Stanford en 1983. Reconnaissant son potentiel commercial, il s'est joint à Moler et à Steve Bangert. Ils ont réécrit MATLAB en C et fondé MathWorks en 1984 pour poursuivre son développement. Ces bibliothèques réécrites étaient connues sous le nom de JACKPAC. En 2000, MATLAB a été réécrit pour utiliser un ensemble de bibliothèques plus récent pour la manipulation de matrice, LAPACK (est une bibliothèque logicielle standard pour l’algèbre linéaire numérique).

Source

Qu'est-ce que CUDA C?

CUDA C utilise également des bibliothèques spécialement développées pour les multiplications matricielles telles que OpenGL (Open Graphics Library). Il utilise également le GPU et Direct3D (sur MS Windows).

Le plate-forme CUDA est conçu pour fonctionner avec des langages de programmation tels que C, C++ et Fortran. Cette accessibilité facilite l’utilisation des ressources GPU par les spécialistes de la programmation parallèle, contrairement aux API précédentes telles que Direct3D et OpenGL, ce qui nécessitait des compétences avancées en programmation graphique. De plus, CUDA supporte des frameworks de programmation tels que OpenACC et OpenCL.

enter image description here

Exemple de flux de traitement CUDA:

  1. Copier les données de la mémoire principale vers la mémoire du processeur graphique
  2. La CPU lance le noyau de calcul GPU
  3. Les cœurs CUDA du GPU exécutent le noyau en parallèle
  4. Copiez les données résultantes de la mémoire du processeur graphique dans la mémoire principale

Comparaison des vitesses d'exécution du processeur et du processeur graphique

Nous avons effectué une analyse de la mesure dans laquelle nous avons mesuré le temps nécessaire à l'exécution de 50 étapes pour des tailles de grille de 64, 128, 512, 1024 et 2048 sur un processeur Intel Xeon X5650, puis à l'aide d'un processeur graphique NVIDIA Tesla C2050.

enter image description here

Pour une taille de grille de 2 048, l'algorithme indique une diminution de 7,5 fois du temps de calcul, passant de plus d'une minute sur le processeur à moins de 10 secondes sur le GPU. Le graphique à l'échelle du journal montre que le processeur est réellement plus rapide pour les petites tailles de grille. Cependant, à mesure que la technologie évolue et mûrit, les solutions GPU sont de plus en plus capables de gérer des problèmes moins importants, une tendance que nous prévoyons de poursuivre.

Source

De l'introduction au guide de programmation CUDA C:

Sous l'impulsion de la demande insatiable du marché pour les graphiques 3D haute définition en temps réel, l'unité de traitement graphique ou GPU programmable a évolué pour devenir un processeur multithreads et multithreads hautement parallèle doté d'une énorme puissance de calcul et d'une bande passante mémoire très élevée, comme illustré par Figure 1 et Figure 2.

Figure 1. Opérations en virgule flottante par seconde pour le processeur et le processeur graphique

enter image description here

Figure 2 . Bande passante mémoire pour le processeur et le processeur graphique

enter image description here

La divergence des capacités en virgule flottante entre le processeur et le processeur graphique s'explique par le fait que le processeur graphique est spécialisé dans les calculs hautement parallèles et intensifs en temps de calcul, ce qui est exactement le résultat du rendu graphique. plutôt que la mise en cache de données et le contrôle de flux, comme illustré schématiquement par Figure 3.

Figure 3 . Le GPU consacre plus de transistors au traitement de données

enter image description here

Plus précisément, le processeur graphique est particulièrement bien adapté pour résoudre les problèmes pouvant être exprimés sous la forme de calculs parallèles de données - le même programme est exécuté sur de nombreux éléments de données en parallèle - avec une intensité arithmétique élevée - le rapport entre les opérations arithmétiques et les opérations en mémoire. Comme le même programme est exécuté pour chaque élément de données, le contrôle de flux sophistiqué est moins nécessaire et, comme il est exécuté sur de nombreux éléments de données et que son intensité arithmétique est élevée, le temps de latence d'accès à la mémoire peut être masqué avec des calculs plutôt que des caches de données volumineuses. .

Le traitement parallèle des données mappe les éléments de données sur des unités d'exécution parallèles. De nombreuses applications qui traitent des ensembles de données volumineux peuvent utiliser un modèle de programmation parallèle pour accélérer les calculs. En rendu 3D, les grands ensembles de pixels et de sommets sont mappés sur des threads parallèles. De même, les applications de traitement d'images et de supports telles que le post-traitement des images rendues, le codage et le décodage vidéo, la mise à l'échelle, la stéréoscopie et la reconnaissance de formes permettent de mapper des blocs d'image et des pixels sur des tâches de traitement parallèles. En fait, de nombreux algorithmes en dehors du domaine du rendu et du traitement des images sont accélérés par le traitement parallèle des données, du traitement général du signal à la simulation physique en passant par la finance ou la biologie computationnelles.

Source

Lecture avancée


Quelques facs intéressantes

J'ai écrit une multiplication de matrice C++ aussi rapide que celle de Matlab, mais cela a pris un certain soin. (Avant Matlab utilisait des GPU pour cela).

Сitation de cette réponse.

2
Bharata