Fortran multiplication de matrice de performance dans les différentes optimisation
Je suis en train de lire le livre "Scientifique de Développement de Logiciels avec Fortran", et il y a un exercice à elle que je pense très intéressant:
"Créer un module Fortran appelé MatrixMultiplyModule. Ajouter trois sous-programmes à elle appelé LoopMatrixMultiply, IntrinsicMatrixMultiply, et MixMatrixMultiply. Chaque routine devrait prendre deux matrices réelles comme argument, effectuer une multiplication de matrice, et de retourner le résultat par un troisième argument. LoopMatrixMultiply doit être écrit entièrement à faire des boucles, et non les opérations de matrice ou intrinsèques des procédures; IntrinsicMatrixMultiply doit être écrit en utilisant le matmul fonction intrinsèque; et MixMatrixMultiply doit être écrite à l'aide de certains faire des boucles et de la fonction intrinsèque dot_product. Écrire un petit programme pour tester les performances de ces trois façons différentes d'effectuer la multiplication de matrice pour différentes tailles de matrices."
J'ai fait quelques test de multiplier de deux rang 2 de la matrice et voici les résultats, en vertu de différentes options d'optimisation:
compiler:ifort version 13.0.0 on Mac
Voici ma question:
Pourquoi sous -O0 ils ont à peu près la même performance mais matmul a énorme gain de performance lors de l'utilisation d'-O3, tout explicite de la boucle et le produit scalaire a moins de performances? Aussi, pourquoi dot_product semble avoir les mêmes performances comparer explicite de faire des boucles?
Le code que j'utilise est la suivante:
module MatrixMultiplyModule
contains
subroutine LoopMatrixMultiply(mtx1,mtx2,mtx3)
real,intent(in) :: mtx1(:,:),mtx2(:,:)
real,intent(out),allocatable :: mtx3(:,:)
integer :: m,n
integer :: i,j
if(size(mtx1,dim=2) /= size(mtx2,dim=1)) stop "input array size not match"
m=size(mtx1,dim=1)
n=size(mtx2,dim=2)
allocate(mtx3(m,n))
mtx3=0.
do i=1,m
do j=1,n
do k=1,size(mtx1,dim=2)
mtx3(i,j)=mtx3(i,j)+mtx1(i,k)*mtx2(k,j)
end do
end do
end do
end subroutine
subroutine IntrinsicMatrixMultiply(mtx1,mtx2,mtx3)
real,intent(in) :: mtx1(:,:),mtx2(:,:)
real,intent(out),allocatable :: mtx3(:,:)
integer :: m,n
integer :: i,j
if(size(mtx1,dim=2) /= size(mtx2,dim=1)) stop "input array size not match"
m=size(mtx1,dim=1)
n=size(mtx2,dim=2)
allocate(mtx3(m,n))
mtx3=matmul(mtx1,mtx2)
end subroutine
subroutine MixMatrixMultiply(mtx1,mtx2,mtx3)
real,intent(in) :: mtx1(:,:),mtx2(:,:)
real,intent(out),allocatable :: mtx3(:,:)
integer :: m,n
integer :: i,j
if(size(mtx1,dim=2) /= size(mtx2,dim=1)) stop "input array size not match"
m=size(mtx1,dim=1)
n=size(mtx2,dim=2)
allocate(mtx3(m,n))
do i=1,m
do j=1,n
mtx3(i,j)=dot_product(mtx1(i,:),mtx2(:,j))
end do
end do
end subroutine
end module
program main
use MatrixMultiplyModule
implicit none
real,allocatable :: a(:,:),b(:,:)
real,allocatable :: c1(:,:),c2(:,:),c3(:,:)
integer :: n
integer :: count, rate
real :: timeAtStart, timeAtEnd
real :: time(3,10)
do n=100,1000,100
allocate(a(n,n),b(n,n))
call random_number(a)
call random_number(b)
call system_clock(count = count, count_rate = rate)
timeAtStart = count /real(rate)
call LoopMatrixMultiply(a,b,c1)
call system_clock(count = count, count_rate = rate)
timeAtEnd = count /real(rate)
time(1,n/100)=timeAtEnd-timeAtStart
call system_clock(count = count, count_rate = rate)
timeAtStart = count /real(rate)
call IntrinsicMatrixMultiply(a,b,c2)
call system_clock(count = count, count_rate = rate)
timeAtEnd = count /real(rate)
time(2,n/100)=timeAtEnd-timeAtStart
call system_clock(count = count, count_rate = rate)
timeAtStart = count /real(rate)
call MixMatrixMultiply(a,b,c3)
call system_clock(count = count, count_rate = rate)
timeAtEnd = count /real(rate)
time(3,n/100)=timeAtEnd-timeAtStart
deallocate(a,b)
end do
open(1,file="time.txt")
do n=1,10
write(1,*) time(:,n)
end do
close(1)
deallocate(c1,c2,c3)
end program
Pas d'autres indicateurs ont été utilisés. Je n'utilise que le drapeau
-O
dans ces cas, par exemple, ifort -O3 sub.f90 main.f90
.Vous pouvez vous assurer que le code est utilisé par l'impression, le vecteur résultant d'un fichier de travail à l'extérieur de l'échéancier des appels.
L'une des raisons de ce genre d'exercice est inclus dans ce genre de livre est pour vous provoquer dans la pensée, et de l'apprentissage, de plus en plus profondément au sujet de la performance du programme: des sujets tels que la façon dont vous structurez votre code dans un langage de haut niveau, comment le compilateur traduit en assembleur, ce que les différentes transformations que le compilateur fait pour les différents niveaux de l'optimisation, et bien plus encore. Alors, qu'en pensez-vous ? Cache, vecteur des instructions, des modèles d'accès mémoire, boucle de dérouler, le tableau de carrelage, de la rigueur de la f-p opérations, toutes sortes de facteurs entrent en jeu et sont mérite d'être étudié.
mon test montre que pour matmul -O3 est beaucoup plus rapide que -O2. Pour 1000x1000 aléatoire réelle de la multiplication de matrice. -O3 prend 0.14 s, -O2 prend 0.48 s
OriginalL'auteur xslittlegrass | 2013-03-22
Vous devez vous connecter pour publier un commentaire.
Il y a plusieurs choses que l'on devrait être conscient de quand en boucle sur les éléments du tableau:
Assurez-vous que la boucle interne est plus consécutifs éléments en mémoire. Dans votre "loop" de l'algorithme, la boucle interne est plus de l'indice k. Depuis matrices sont mis en mémoire sous forme de colonnes (premier indice variant plus rapidement lorsque vous allez à travers la mémoire), l'accès à une nouvelle valeur de k pourriez avoir besoin de charger une nouvelle page dans le cache. Dans ce cas, vous pouvez optimiser votre algorithme par la réorganisation de la boucle tant que:
maintenant, la boucle interne est plus consécutifs éléments dans la mémoire (la
mtx2(k,j)
valeur sera probablement être récupéré qu'une seule fois avant la boucle interne par le compilateur, si non vous pouvez le stocker dans une variable temporaire avant la boucle)Assurez-vous que l'ensemble de la boucle peut s'intégrer dans le cache afin d'éviter trop de défauts de cache. Cela peut être fait par le blocage de l'algorithme. Dans ce cas, une solution pourrait être par exemple:
auquel cas la performance dépendra de la taille de
ichunk
, en particulier pour les grandes suffisamment de matrices (vous pouvez même bloquer la boucle intérieure, c'est juste un exemple).Assurez-vous que le travail nécessaire pour effectuer la boucle est beaucoup plus petit que le travail à l'intérieur de la boucle. Ce problème peut être résolu par le "déroulement de la boucle', c'est à dire la combinaison de plusieurs états dans une itération de la boucle. Généralement, le compilateur peut le faire en fournissant le drapeau
-funroll-loops
.Si j'utilise le code ci-dessus et de les compiler avec les drapeaux
-O3 -funroll-loops
, je reçois une performance légèrement meilleure que avecmatmul
.La chose importante à retenir de ces trois est le premier point sur la boucle de commande, puisque c'est quelque chose qui va affecter les performances dans d'autres cas d'utilisation, et le compilateur ne peut généralement résoudre ce problème. Le déroulement de la boucle, vous pouvez laisser le compilateur (mais de le tester, car cela ne permet pas toujours d'augmenter les performances). Comme pour le second point, puisque cela dépend du matériel, vous ne devriez pas (en général) essayez de mettre en œuvre un moyen très efficace de multiplication de matrice de vous-même et, au lieu de considérer l'utilisation d'une bibliothèque comme par exemple l'atlas, qui permet d'optimiser pour la taille de la mémoire cache, ou un vendeur de bibliothèque comme MKL ou ACML.
OriginalL'auteur steabert