Multiplication de masortingces avec des blocs

Ceci est mon code pour accélérer la multiplication masortingcielle, mais il est seulement 5% plus rapide que le simple. Que puis-je faire pour le booster le plus possible?

* Les tables sont utilisées par exemple sous la forme: C [sub2ind (i, j, n)] pour la position C [i, j] .

void masortingxMultFast(float * const C, /* output masortingx */ float const * const A, /* first masortingx */ float const * const B, /* second masortingx */ int const n, /* number of rows/cols */ int const ib, /* size of i block */ int const jb, /* size of j block */ int const kb) /* size of k block */ { int i=0, j=0, jj=0, k=0, kk=0; float sum; for(i=0;i<n;i++) for(j=0;j<n;j++) C[sub2ind(i,j,n)]=0; for(kk=0;kk<n;kk+=kb) { for(jj=0;jj<n;jj+=jb) { for(i=0;i<n;i++) { for(j=jj;j<jj+jb;j++) { sum=C[sub2ind(i,j,n)]; for(k=kk;k<kk+kb;k++) sum += A[sub2ind(i,k,n)]*B[sub2ind(k,j,n)]; C[sub2ind(i,j,n)]=sum; } } } } } // end function 'matrixMultFast4' 

* Il est écrit en C et doit prendre en charge C99.

    Il y a beaucoup de choses que vous pouvez faire pour améliorer l’efficacité de la multiplication masortingcielle.

    Pour examiner comment améliorer l’algorithme de base, examinons d’abord nos options actuelles. Bien entendu, l’implémentation naïve a 3 boucles avec une complexité temporelle de l’ordre de O(n^3) . Il existe une autre méthode appelée la méthode de Strassen qui permet une accélération appréciable et qui a l’ordre de O(n^2.73) (mais qui est inutile dans la pratique car elle n’offre aucun moyen appréciable d’optimisation).

    C’est en théorie. Considérons maintenant comment les masortingces sont stockées en mémoire. Row major est la norme, mais vous trouvez aussi colonne major. En fonction du schéma, la transposition de votre masortingce peut améliorer la vitesse en réduisant le nombre d’erreurs dans le cache. La multiplication de masortingce en théorie est juste un groupe de produits de points vectoriels et d’addition. Le même vecteur doit être exploité par plusieurs vecteurs. Il est donc logique de conserver ce vecteur en mémoire cache pour un access plus rapide.

    Maintenant, avec l’introduction de plusieurs cœurs, du parallélisme et du concept de cache, le jeu change. Si nous regardons un peu plus près, nous voyons qu’un produit scalaire n’est rien d’autre qu’un tas de multiplications suivies de sommations. Ces multiplications peuvent être effectuées en parallèle. Par conséquent, nous pouvons maintenant regarder le chargement parallèle des nombres.

    Maintenant rendons les choses un peu plus compliquées. Quand on parle de multiplication de masortingce, il y a une distinction entre taille simple et virgule flottante double. Souvent, le premier est de 32 bits tandis que le dernier, de 64 (bien sûr, cela dépend de la CPU). Chaque CPU n’a qu’un nombre fixe de registres, ce qui signifie que plus vos chiffres sont grands, moins vous pouvez tenir dans la CPU. La morale de l’histoire est la suivante: tenez-vous-en à une virgule flottante, sauf si vous avez vraiment besoin de doubler.

    Maintenant que nous avons exploré les bases de l’ajustement de la multiplication masortingcielle, ne vous inquiétez pas. Vous n’avez rien besoin de faire de ce qui a été discuté ci-dessus car il existe déjà des sous-programmes pour le faire. Comme mentionné dans les commentaires, il y a GotoBLAS, OpenBLAS, MKL d’Intel et le cadre Accelerate d’Apple. MKL / Accelerate sont des solutions propriétaires, mais OpenBLAS est une alternative très compétitive.

    Voici un bel exemple qui multiplie 2 masortingces de 8 000 x 8 000 en quelques millisecondes sur mon Macintosh:

     #include  #include  #include  #include  #include  int SIZE = 8192; typedef float point_t; point_t* transpose(point_t* A) { point_t* At = (point_t*) calloc(SIZE * SIZE, sizeof(point_t)); vDSP_mtrans(A, 1, At, 1, SIZE, SIZE); return At; } point_t* dot(point_t* A, point_t* B) { point_t* C = (point_t*) calloc(SIZE * SIZE, sizeof(point_t)); int i; int step = (SIZE * SIZE / 4); cblas_sgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans, SIZE/4, SIZE, SIZE, 1.0, &A[0], SIZE, B, SIZE, 0.0, &C[0], SIZE); cblas_sgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans, SIZE/4, SIZE, SIZE, 1.0, &A[step], SIZE, B, SIZE, 0.0, &C[step], SIZE); cblas_sgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans, SIZE/4, SIZE, SIZE, 1.0, &A[step * 2], SIZE, B, SIZE, 0.0, &C[step * 2], SIZE); cblas_sgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans, SIZE/4, SIZE, SIZE, 1.0, &A[step * 3], SIZE, B, SIZE, 0.0, &C[step * 3], SIZE); return C; } void print(point_t* A) { int i, j; for(i = 0; i < SIZE; i++) { for(j = 0; j < SIZE; j++) { printf("%f ", A[i * SIZE + j]); } printf("\n"); } } int main() { for(; SIZE <= 8192; SIZE *= 2) { point_t* A = (point_t*) calloc(SIZE * SIZE, sizeof(point_t)); point_t* B = (point_t*) calloc(SIZE * SIZE, sizeof(point_t)); srand(getpid()); int i, j; for(i = 0; i < SIZE * SIZE; i++) { A[i] = ((point_t)rand() / (double)RAND_MAX); B[i] = ((point_t)rand() / (double)RAND_MAX); } struct timeval t1, t2; double elapsed_time; gettimeofday(&t1, NULL); point_t* C = dot(A, B); gettimeofday(&t2, NULL); elapsed_time = (t2.tv_sec - t1.tv_sec) * 1000.0; // sec to ms elapsed_time += (t2.tv_usec - t1.tv_usec) / 1000.0; // us to ms printf("Time taken for %d size matrix multiplication: %lf\n", SIZE, elapsed_time/1000.0); free(A); free(B); free(C); } return 0; } 

    À ce stade, je devrais également mentionner SSE (Streaming SIMD Extension), qui est fondamentalement quelque chose que vous ne devriez pas faire si vous n’avez pas travaillé avec l’assemblage. En gros, vous vectorisez votre code C pour travailler avec des vecteurs plutôt que des entiers. Cela signifie que vous pouvez utiliser des blocs de données plutôt que des valeurs uniques. Le compilateur abandonne et traduit simplement votre code tel quel sans faire ses propres optimisations. Si cela est fait correctement, cela peut accélérer votre code comme jamais auparavant - vous pouvez même toucher au plancher théorique de O(n^2) ! Mais il est facile d’abuser de l’ESS, et la plupart des gens le font malheureusement, ce qui aggrave le résultat final.

    J'espère que cela vous motive à aller plus loin. Le monde de la multiplication masortingcielle est vaste et fascinant. Ci-dessous, je joins des liens pour une lecture ultérieure.

    1. OpenBLAS
    2. En savoir plus sur l'ESS
    3. Insortingnsics d'Intel