Dépannage du multithreading à l'aide de Parallel AssignJe suis sur un système Mac essayant de faire fonctionner le multithreading sur une grande table de données (> 8MM) sur des opérations par ligne. Ce ne sont que des calculs simples comme la division ou la copie de valeurs telles quelles. Je ne peux pas joindre mes données propriétaires, mais dans cet article, j'inclurai dans le JSL une matrice aléatoire de la même taille que mon jeu de données mais avec des données aléatoires.
Names Default To Here(1);<BR /><BR />//For JMP community.
dt = J(Random Integer(6000000 , 8000000) , 5 , Random Integer(10)); <BR />dt = As Table(dt); <BR /><BR />//dt = Current Data Table(); //Table needs to be formatted such that data table has 5 columns with names X , Y , Z , Filename , Radius.
//columns must be in that order for matrix functionality to work
edgeExclusion = 0.01; //in units of radial meters
stepSize = 0.0025; //in meters; this is the resolution of the desired particle size analysis. This is the side length of the square of the defect area resolution desired.
waferSize = 0.2; //in meters, diameter
If( Floor( waferSize / stepSize ) != Ceiling( waferSize / stepSize ) , Throw( "!Defect resolution size not a common factor of wafer size"));
//Takes data table as matrix (fDT) and returns matrix of x, y, stddev, stddev points, and UID.
//Requires datatable to have X , Y , Z , Filename , Radius , and UID so that matrix fDT has X , Y , Z , Radius , and UID; in that order.
stdDevCalculator2 = Function ( { fDT , fFirstRow , fLastRow , fWS , fEE , fSS } , {Default Local} ,
fSubDT = fDT[ fFirstRow :: fLastRow , 0];
outputMat = J(0,5);
For( i = 1 , i <= N Rows(fSubDT) , i++ ,
//If( fSubDT[i,4] > (fWS/2)-fEE , Continue()); //Possibly remove to keep number of columns same for Parallel Assign
rowMat = Matrix( { Floor(fSubDT[i,1]/fSS) , Floor(fSubDT[i,2]/fSS) , fSubDT[i,3] , fSubDT[i,4] , fSubDT[i,5] } );
rowMat = Shape(rowMat,1,5);
If(N Rows(outputMat) == 0 , outputMat = rowMat , Concat To(outputMat , rowMat));
);
outputMat[1,0];
);
t1 = Tick Seconds();
numThreads = 8;
//Number of rows to delete to make Parallel Assign function operate. Trimming original data table to even multiple of thread count will allow tOutMat to be a square matrix,
//so that each thread is assigning the same number of columns.
delNum = N Rows(dt) - (Floor(N Rows(dt)/numThreads)*numThreads);
dt << Select Randomly(delNum);
dt << Delete Rows(); //Trim data set
numCols = 5 * (N Rows(dt)/numThreads); //5 for number of colums in original stacked data table.
//outMat = stdDevCalculator2(dtMat , 1 , N Rows(dtMat) , waferSize , edgeExclusion , stepSize); //For single threaded testing
dtMat = dt << GetAsMatrix;
tOutMat = J(numThreads,numCols);
Parallel Assign(
{tDT = dtMat, tWS = waferSize, tEE = edgeExclusion, tSS = stepSize, tSDC = Name Expr(stdDevCalculator2), tNum = numThreads },
tOutMat[i,0] = tSDC( tDT, 1 + Floor((i - 1) * N Rows(tDT) / tNum) , Floor( i * N Rows(tDT) / tNum) , tWS, tEE, tSS );
);
Show(tOutMat);
Write( "elapsed time=", Tick Seconds() - t1 );Mon problème est que sur mon système Mac, il semble que l'affectation parallèle fonctionne car toutes mes utilisations principales passent à 100% lorsque ce script s'exécute. Cependant, après lui avoir donné suffisamment de temps pour se terminer (longtemps après la version à un seul thread), je dois forcer à quitter.
J'ai joué un peu pour comprendre comment JSL préfère que je "injecte" un ensemble de valeurs par ligne dans une autre matrice. Prenons l'exemple ci-dessous :
Names Default to Here(1);
mat1 = J(3,5,0);
mat2 = J(1,5,Random Integer(5));
show(mat1);
Show(mat2);
mat1[2,0] = mat2[1,0];
Show(mat1);
mat1 aura maintenant sa deuxième ligne une copie complète de mat2. J'ai essayé d'émuler ce même type d'affectation de matrice en retournant une seule ligne avec N Rows(table)/N Threads. Quelqu'un a-t-il une idée de la raison pour laquelle je n'arrive pas à faire fonctionner cela?
Re : Dépannage du multithreading à l'aide de Parallel Assignre-déroutant sur le JSL lent : je pense que la valeur tDT est un énorme matrice qui est en train d'être copiée énorme nombre de fois pour passer à la fonction tf.8E6 * Les numéros 8E6 sont copiés. Les threads attendent un accès mémoire.
tOutMat[i,j] = tf( tDT, i , j , tSS )
Cette copie ne se produit pas dans le JSL rapide.
Re : Dépannage du multithreading à l'aide de Parallel AssignSalut @msulzer_bsi
Une déclaration générale : le multithreading est une mauvaise idée lorsque le problème que vous essayez de résoudre n'est pas parallélisable, ou lorsque le problème que vous essayez de calculer est si petit que la création de threads coûtera plus de temps que le calcul dans un seul thread.
Si votre problème peut être divisé en parties pouvant s'exécuter en parallèle, cela peut être une bonne idée de le faire, mais cela dépend de la quantité pouvant s'exécuter en parallèle et de la facilité avec laquelle il est possible de joindre les résultats du sous-problème. Si votre algorithme nécessite plusieurs jointures (à un thread) pour se diviser en nouveaux sous-problèmes sur de nouveaux threads, il doit être très gourmand en calculs pour valoir la peine de paralléliser, si votre résultat, en revanche, est juste un liste des résultats du sous-problème rejoint une fois, alors il y a du potentiel.
Je ne vois pas beaucoup d'avantages dans votre opération et l'amélioration de la vitesse que vous gagneriez sur 2 secondes ne pourrait pas être beaucoup, alors pourquoi voulez-vous faire cet effort et bloquer avec htis d'autres tâches qui pourraient/se seraient exécutées sur les autres threads. Parfois, cela ne vaut pas la peine de gagner 0,5 seconde de plus pour ce que vous devez faire. À moins qu'il ne s'agisse d'une application à haut débit où vous obtenez tous les 2-3 autres blocs de données à traiter, je ne vois pas de bonne raison d'accélérer cette opération en raison de données de> 8MM comme vous l'avez dit.
Re : Dépannage du multithreading à l'aide de Parallel AssignSalut @Craige_Hales ,
J'ai utilisé vos conseils pour créer une définition de fonction dans l'affectation parallèle. Voir ci-dessous:
Names Default To Here(1);
dt = Current Data Table(); //Table needs to be formatted such that data table has 5 columns with names X , Y , Z , Filename , Radius.
//columns must be in that order for matrix functionality to work
edgeExclusion = 0.01; //in units of radial meters
stepSize = 0.0025; //in meters; this is the resolution of the desired particle size analysis. This is the side length of the square of the defect area resolution desired.
waferSize = 0.2; //in meters, diameter
If( Floor( waferSize / stepSize ) != Ceiling( waferSize / stepSize ) , Throw( "!Defect resolution size not a common factor of wafer size"));
t1 = Tick Seconds();
numThreads = 8;
//Number of rows to delete to make Parallel Assign function operate. Trimming original data table to even multiple of thread count will allow tOutMat to be a square matrix,
//so that each thread is assigning the same number of columns.
delNum = N Rows(dt) - (Floor(N Rows(dt)/numThreads)*numThreads);
If( delNum != 0 , dt << Select Randomly(delNum));
dt << Delete Rows(); //Trim data set
dtMat = dt << GetAsMatrix;
dtMat = Shape(dtMat , numThreads);
tOutMat = J(numThreads , N Cols(dtMat));
Parallel Assign(
{tDT = dtMat, tSS = stepSize, tf = Function ( { fDT , x , y , fSS } ,
If( Mod(y,5) == 1 , Floor(fDT[x,y]/fSS) ,
Mod(y,5) == 2 , Floor(fDT[x,y]/fSS) ,
fDT[x,y];
);
); },
tOutMat[i,j] = tf( tDT, i , j , tSS );
);
Show(tOutMat);
Write( "elapsed time=", Tick Seconds() - t1 );Je fais maintenant en sorte que le script s'exécute et ne me donne aucune sortie bizarre ni ne génère d'erreur. J'ai réévalué ce que vous avez expliqué sur l'utilisation de l'affectation parallèle pour évaluer chaque combinaison de la matrice de sortie en fonction de l'emplacement de la matrice de sortie.Dans mon cas, je ne souhaite effectuer des calculs que sur les valeurs des deux premières colonnes de la table de données originale à longues lignes, d'où les instructions Mod() ==1 et Mod() ==2. Ce n'est pas le script le plus flexible, en raison des exigences strictes de mise en forme du tableau, mais c'est un problème pour plus tard.
Cependant, maintenant, j'obtiens un phénomène étrange où lorsque j'exécute la version multithread du script, j'obtiens des temps d'exécution d'environ 200 secondes. Lorsque je réécris l'instruction d'assignation parallèle pour qu'elle soit une boucle for imbriquée comme ci-dessous en tant que script monothread :
For(i = 1 , i <= numThreads , i++ ,
For(j = 1 , j <= numCols , j++ ,
If( Mod(j,5) == 1 , tOutMat[i,j] = Floor(dtMat[i,j]/stepSize) ,
Mod(j,5) == 2 , tOutMat[i,j] = Floor(dtMat[i,j]/stepSize) ,
tOutMat[i,j] = dtMat[i,j];
);
);
);
Je peux obtenir des temps d'exécution d'environ 2 secondes... J'ai façonné la matrice d'entrée même si elle n'est pas nécessaire afin de pouvoir comparer plus uniformément le processus des opérations monothread et multithread. Je ne comprends pas quel type de surcharge se produit qui rend la version multithread beaucoup plus lente. J'ai un peu réduit le calcul matriciel depuis le moment où j'ai posté ma question pour la première fois. Avez-vous des idées?
Merci msulzer
Re : Dépannage du multithreading à l'aide de Parallel AssignSalut Craige,
Merci beaucoup pour votre réponse à ma question; J'espérais qu'au moins vous verriez le fil parce que mon apprentissage sur Parallel Assign était dû en grande partie à certains exemples que vous avez publiés précédemment. Je vais essayer de digérer la signification fondamentale de vos déclarations et je vous reviendrai avec tous les résultats que je peux appliquer à mon cas spécifique.
Re : Dépannage du multithreading à l'aide de Parallel AssignJe pense que vous espérez quelque chose que Parallel Assign ne peut pas faire. Ce code
tOutMat[i,0] =
dans le Parallel Assign m'a surpris; la seule chose à laquelle Parallel Assign s'attend est des noms de variables JSL pour ces indices, pas un zéro. Il semble que le zéro soit discrètement ignoré et que le côté droit de l'affectation n'ait aucun moyen de savoir quel est le 2e indice.Voici quelques exemples; vous pouvez changer le 2ème indice à zéro et voir qu'il est ignoré.
Un. Utilisez une variable i, locale à chaque thread, et incrémentez-la après chaque utilisation. Les valeurs d'indice j,k (ou j,0) ne sont pas utilisées pour déterminer la valeur calculée pour l'élément.
x = J( 7, 11 );
Parallel Assign( {i = 0}, x[j, k] = (i++) );
Show( x );x =
[ 0 0 0 0 1 1 1 1 2 2 2,
2 3 3 3 3 4 4 4 4 5 5,
5 5 6 6 6 6 7 7 7 7 8,
8 8 8 9 9 9 9 10 10 10 10,
11 11 11 11 12 12 12 12 13 13 13,
13 14 14 14 14 15 15 15 15 16 16,
16 16 17 17 17 17 18 18 18 18 19] ;
Deux. Similaire, montre la même chose d'une autre manière. Les threads obtiennent chacun une valeur unique qui reste inchangée. h=4 indique le nombre de processeurs disponibles. Vous ne devriez pas compter sur JMP pour toujours entrelacer les calculs comme indiqué, mais c'est comme ça pour l'instant.
x = J( 7, 11 );
h = 0;
Parallel Assign( {i = (h++)}, x[j, k] = i );
Show( x, h );x =
[ 0 1 2 3 0 1 2 3 0 1 2,
3 0 1 2 3 0 1 2 3 0 1,
2 3 0 1 2 3 0 1 2 3 0,
1 2 3 0 1 2 3 0 1 2 3,
0 1 2 3 0 1 2 3 0 1 2,
3 0 1 2 3 0 1 2 3 0 1,
2 3 0 1 2 3 0 1 2 3 0] ;
h = 4 ;
Trois. Utilisez une seule variable globale. Incrémenter après chaque utilisation. Les threads se verrouillent mutuellement et peuvent ou non s'exécuter dans un ordre particulier.
x = J( 7, 11 );
global:g = 0;
Parallel Assign( {i = (h++)}, x[j, k] = (global:g++) );
Show( x, g );x =
[ 0 20 39 58 1 21 40 59 2 22 41,
60 3 23 42 61 4 24 43 62 5 25,
44 63 6 26 45 64 7 27 46 65 8,
28 47 66 9 29 48 67 10 30 49 68,
11 31 50 69 12 32 51 70 13 33 52,
71 14 34 53 72 15 35 54 73 16 36,
55 74 17 37 56 75 18 38 57 76 19] ;
g = 77 ;
Dans tous les cas ci-dessus, remplacer k par 0 ne change rien. En interne, Parallel Assign calcule toujours toutes les paires d'index et demande à l'un des threads de calculer une valeur pour chaque paire.
Si vous continuez à explorer cette voie, je vous suggère de créer des instances distinctes de votre fonction pour chaque thread, quelque chose comme
x = J( 2, 3 );
Parallel Assign( {f = Function( {x, y}, x * y )}, x[i, j] = f( i, j ) );
Show( x );x = [1 2 3, 2 4 6] ;
ce qui fait que le f local de chaque thread a une copie distincte de la fonction JSL. Je pense que le nameexpr ne fait pas de copies séparées, et ils peuvent besoin être séparés pour fonctionner correctement en parallèle.
Vous pouvez également utiliser une affectation à un élément de tableau global. Utilisez un tableau unidimensionnel de la bonne longueur pour piloter l'affectation parallèle : (notez également la valeur de droite à plusieurs instructions entre parenthèses)
global:results = J( 5, 7, . );
driver = J( N Rows( results ), 1, 0 );
h = 0;
Parallel Assign(
{procNum = (1 + h++), NC = N Cols( results )},
driver[j] =
(
For( k = 1, k <= NC, k += 1,
global:results[j, k] = procNum * 100 + j * 10 + k
);
procNum// value assigned to driver
)
);
Show( global:results, h, driver );global:résultats =
[ 111 112 113 114 115 116 117,
221 222 223 224 225 226 227,
331 332 333 334 335 336 337,
441 442 443 444 445 446 447,
151 152 153 154 155 156 157] ;
h = 4 ;
conducteur = [1, 2, 3, 4, 1] ;
Mais les variables globales vont probablement le rendre aussi lent qu'un thread unique.
Et enfin: Parallel Assign essaie de vous empêcher d'écrire du code non sécurisé qui pourrait permettre à deux threads d'accéder à une structure interne, en même temps, d'une manière qui produira des résultats incorrects ou un plantage. Il est possible d'écrire du JSL qui échappera aux défenses de Parallel Assign... économisez souvent votre travail si vous repoussez les limites.