Pourquoi la parallélisation? C'est pour accélérer le processus. Quand j'ai introduit le parallèle MPI auparavant, on m'a demandé s'il y avait un parallèle plus simple, donc je vais introduire un parallèle qui peut être facilement fait s'il y a plusieurs cœurs. Essayez-le sur une machine avec un processeur de 2core ou plus. Il est destiné à Linux, mais essayez «défi» sur un autre système d'exploitation.
Préparez le programme cible. Si vous avez un programme qui utilise des tableaux, essayez-le. Je voudrais avoir une taille de tableau d'environ 1000. Sinon, j'ai préparé le programme suivant. Veuillez copier et essayer.
#include <stdio.h>
#include <time.h>
int main()
{
int n,m,i,j,k;
double a[5000][600];
double b[600][5000];
double us,t0,t1,t2,sum;
t0=clock();
us = 10.0 ;
n=5000 ;
m=600 ;
for (j =0;j<m;j++){
for (i =0;i<n;i++){
b[j][i] = (j+i+2)/10.0 ;
}
}
for (k=0;k<200;k++) {
for (j =0;j<m;j++){
for (i =0;i<n;i++){
a[i][j] = b[j][i] + us ;
}
}
us = us + 1.2 ;
}
sum=0.0 ;
for (j =0;j<m;j++){
for (i =0;i<n;i++){
sum=sum+a[i][j] ;
}
}
t1=clock();
t2 = (t1 - t0) * 1.0e-6 ;
printf(" TIME=%f data= %f\n",t2,sum);
}
Ce programme n'a aucune signification physique. C'est un programme d'entraînement qui consiste simplement à mettre des valeurs dans un tableau et à les résumer.
Basons ceci sur. Appelons le fichier sample2.c. (Tout * .c)
[]$ gcc sample2.c
[]$ ./a.out
Segmentation fault
[]$
Ah, comme c'est innocent. .. Supprimons les restrictions.
[]$ ulimit -s unlimited
[]$ ./a.out
TIME=3.898616 data= 1586700000.000000
[]$
Ajoutons une option d'optimisation.
[]$ gcc -O2 sample2.c
[]$ ./a.out
TIME=3.840020 data= 1586700000.000000
[]$
L'option d'optimisation ne semble pas très bien fonctionner. Déroulons-nous un peu.
[]$ vi sample2a.c (sample2.J'ai apporté des modifications à c et je l'ai enregistré)
[]$ gcc sample2a.c
[]$ ./a.out
TIME=1.300404 data= 1586700000.000000
[]$
Cela semble fonctionner. Ajoutons maintenant une optimisation.
[]$ gcc -O2 sample2a.c
[]$ ./a.out
TIME=1.192950 data= 1586700000.000000
[]$
Cela semble bien fonctionner. Puisque cette fois le thème est SMP parallèle, je vais aborder le réglage pour accélérer plus tard, mais j'aimerais le faire à une date ultérieure s'il y a une opportunité.
Maintenant, courons en parallèle.
Mettons une ligne d'instruction de parallélisation dans un programme simple. Pour le moment, la partie la plus lourde est la boucle suivante, insérez donc la ligne d'instructions de parallélisation OpenMP comme suit.
[]$ cp sample2.c sample2b.c
[]$ vi sample2b.c
for (k=0;k<200;k++) {
#pragma omp parallel for private(i,j)
for (j =0;j<m;j++){
for (i =0;i<n;i++){
a[i][j] = b[j][i] + us ;
}
}
us = us + 1.2 ;
}
Il y a 3 boucles, mais c'est la triple boucle la plus lourde, alors essayez d'échanger les positions spécifiées. De plus, il existe différentes options et instructions, veuillez donc vérifier et essayer vous-même. Il peut être spécifié dans d'autres boucles, mais l'effet est limité en raison de la faible granularité. Mais essayez-le. Ajoutez la réduction (+: somme) dans la boucle de somme. C'est comme ça.
sum=0.0 ;
#pragma omp parallel for private(i,j) reduction(+:sum)
for (j =0;j<m;j++){
for (i =0;i<n;i++){
sum+=a[i][j] ;
}
}
Ici, la ligne d'instructions à saisir n'est que le formulaire de base. Maintenant, compilons. Ajoutez "-fopenmp" à l'option.
[]$ gcc -O2 -fopenmp sample2b.c
C'est acceptable.
Lançons-le.
[]$ ./a.out
TIME=3.932937 data= 1586700000.000000
[]$
Je pense que cela fonctionne normalement. Ensuite, fonctionnons enfin en parallèle. Tout d’abord, parce que c’est magique
[]$ export OMP_NUM_THREADS=2
[]$ ./a.out
TIME=3.945268 data= 1586700000.000000
[]$
Hmm, il est tard. .. .. ?? Oh j'ai oublié. Dans le cas de C, le temps de thread a été ajouté. Utilisez la commande de mesure du temps. La partie affichée comme "réelle" est le temps d'exécution. Continuons à courir.
[]$ export OMP_NUM_THREADS=1
[]$ time ./a.out
TIME=3.826071 data= 1586700000.000000
real 0m3.836s
user 0m3.816s
sys 0m0.012s
[]$ export OMP_NUM_THREADS=2
[]$ time ./a.out
TIME=3.908159 data= 1586700000.000000
real 0m1.960s
user 0m3.895s
sys 0m0.016s
[]$ export OMP_NUM_THREADS=4
[]$ time ./a.out
TIME=5.014193 data= 1586700000.000000
real 0m1.284s
user 0m4.974s
sys 0m0.044s
[]$
Comment était. Avez-vous été plus rapide? Si vous avez une machine avec plus de cœurs, essayez jusqu'au nombre de cœurs. La machine que j'ai utilisée cette fois-ci était de 4 cœurs, donc c'était un peu plus rapide, mais je pense que si vous avez le nombre de cœurs, vous pouvez parfaitement fonctionner. Essayez d'agrandir un peu le tableau. Si le résultat change, cela signifie que vous placez la ligne d'instruction dans la pièce qui ne peut pas être parallélisée. Même dans ce cas, il est possible qu'il soit correctement parallélisé, alors essayez-le. S'il n'est pas parallélisé, la ligne d'instruction peut être mal écrite ou gcc est peut-être trop ancien pour le prendre en charge. Lorsque vous demandez la version, vous verrez généralement plus de chiffres que l'exemple suivant. Vérifiez s'il vous plaît.
[]$ gcc --version
gcc (GCC) 4.9.3
Copyright (C) 2015 Free Software Foundation, Inc.
Si vous avez besoin de beaucoup de traitement et que vous avez suffisamment de CPU dans le nœud, veuillez envisager le parallèle avec OpenMP (introduit cette fois). De plus, si l'environnement de la machine le permet, veuillez considérer MPI (introduction précédente). Si la quantité de données n'est pas très importante, il peut être plus rapide de simplement dérouler. Veuillez envisager d'accélérer en fonction de la quantité de données.
Si vous vous inquiétez du déroulement, veuillez vous reporter à ce qui suit. Ceci est un exemple de 8 étapes. Augmentez la densité de calcul dans la boucle et laissez-la calculer à la fois. Le nombre optimal d'étapes dépend du calcul, alors essayez-le. Notez que le nombre d'arrangements est divisé par le nombre d'étapes. Sinon, si vous ne faites pas le fractionnement correctement, vous obtiendrez une erreur de segmentation ou le résultat changera.
for (k=0;k<200;k++) {
for (j =0;j<m;j+=8){
for (i =0;i<n;i++){
a[i][j] = b[j][i] + us ;
a[i][j+1] = b[j+1][i] + us ;
a[i][j+2] = b[j+2][i] + us ;
a[i][j+3] = b[j+3][i] + us ;
a[i][j+4] = b[j+4][i] + us ;
a[i][j+5] = b[j+5][i] + us ;
a[i][j+6] = b[j+6][i] + us ;
a[i][j+7] = b[j+7][i] + us ;
}
}
us = us + 1.2 ;
}
La boucle la plus interne est maintenue continue afin qu'elle puisse être traitée dans le pipeline, et la boucle externe est ignorée. L'exemple utilise volontairement un tableau avec des indices sans correspondance, mais il est plus rapide de les aligner et le blocage du réglage du cache semble être efficace. Essayez-le. C'est un thème parallèle, alors vérifions-le après le réglage.
[]$ gcc -fopenmp sample2a.c
[]$ export OMP_NUM_THREADS=2
[]$ time ./a.out
TIME=2.066413 data= 1586700000.000000
real 0m1.038s
user 0m2.053s
sys 0m0.016s
[]$ export OMP_NUM_THREADS=4
[]$ time ./a.out
TIME=5.264482 data= 1586700000.000000
real 0m1.344s
user 0m5.206s
sys 0m0.062s
[]$
4 Il est devenu lent en parallèle. On pense que c'est parce que la granularité du traitement est devenue plus petite et que les procédures (fork et join) avant et après l'augmentation de la parallélisation ont dépassé l'effet de la parallélisation. La valeur limite autour de cela dépend du contenu de traitement, de la quantité de traitement et des performances de la machine, veuillez donc la vérifier et l'utiliser en fonction de votre propre environnement. Cette fois, il a été réalisé sur une machine i5 4core + VirtualBox + vinelinux.
c'est tout
Recommended Posts