5.3.1.2.2 : La fonction de calcul avec des intrinsèques pour les blocs


Commençons avec la documentation (j'insisterai toujours) suivit de la définition de notre fonction :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
///Propagate the U and V species in the matVecVecU and matVecV
/**	@param[out] outMatVecU : updated matrix U version (with vectorial neighbours)
 * 	@param[out] outMatVecV : updated matrix V version (with vectorial neighbours)
 * 	@param matVecVecU : input of matrix U (with vectorial neighbours)
 * 	@param matVecV : input of matrix V (with vectorial neighbours)
 * 	@param nbRow : number of rows of the matrices
 * 	@param nbCol : number of columns of the matrices
 * 	@param matBroadcastDeltaSquare : matrix of the delta square values (with broadcast neighbours)
 * 	@param nbStencilRow : number of rows of the matrix matBroadcastDeltaSquare
 * 	@param nbStencilCol : number of columns of the matrix matBroadcastDeltaSquare
 * 	@param diffusionRateU : diffusion rate of the U specie
 * 	@param diffudionRateV : diffusion rate of the V specie
 * 	@param feedRate : rate of the process which feeds U and drains U, V and P
 * 	@param killRate : rate of the process which converts V into P
 * 	@param dt : time interval between two steps
 * 	@param padding : padding of the block
*/
void grayscott_propagation_block(float * outMatVecU, float * outMatVecV, const float * matVecVecU, const float * matVecVecV, long nbRow, long nbCol,
		       const float * matBroadcastDeltaSquare, long nbStencilRow, long nbStencilCol,
		       float diffusionRateU, float diffusionRateV, float feedRate, float killRate, float dt, size_t padding)
{


Ici, nous n'avons pas besoin de dire au compilateur que nos pointeurs sont alignés car l'utilisation des fonction intrinsèques va le forcer à vectoriser.

Nous déterminons les offset de notre stencil (le nombre de couches à partir de la cellule centrale) :

1
2
	long offsetStencilRow((nbStencilRow - 1l)/2l);
	long offsetStencilCol((nbStencilCol - 1l)/2l);


Nous découpons nos colonnes en fonction de la taille des registres vectoriels. Dans le cas d'une matrice où les voisins sont vectoriels, le nombre de colonne sera toujours proportionnel à la taille des registres vectoriels, nous n'avons donc pas besoin de prendre en compte cela avec un calcul dit de tail :

1
	long nbVecCol(nbCol/PLIB_VECTOR_SIZE_FLOAT);


Nous initialisons des constantes vectorielles, dont certaines correspondent au scalaires passés en paramètres :

1
2
3
4
5
6
	PRegVecf vecOne(plib_broadcast_ss(1.0f));
	PRegVecf vecFeedRate(plib_broadcast_ss(feedRate));
	PRegVecf vecKillRate(plib_broadcast_ss(killRate));
	PRegVecf vecDiffudionRateU(plib_broadcast_ss(diffusionRateU));
	PRegVecf vecDiffudionRateV(plib_broadcast_ss(diffusionRateV));
	PRegVecf vecDt(plib_broadcast_ss(dt));


Nous bouclons sur les lignes de nos matrices pour mettre à jour toutes nos cellules. Il faut se rappeler que nous avons une première et une dernière ligne supplémentaires dans nos matrices (voir section 4.3.1) :

1
	for(long i(1l); i < nbRow - 1l; ++i){


Il faut maintenant déterminer les bornes de nos calculs en ligne (voir section 4.1.1.1) :

1
2
		long firstRowStencil(std::max(i - offsetStencilRow, 0l));
		long lastRowStencil(std::min(i + offsetStencilRow + 1l, nbRow));


Nous bouclons sur les colonnes de nos matrices pour mettre à jour toutes nos cellules. Comme chaque bloc possède un contour en lecture seule, on peut ignorer la première et la dernière colonne, ce qui simplifie les calcul :

1
		for(long j(1l); j < nbVecCol - 1l; ++j){


Il faut maintenant déterminer les bornes de nos calculs en colonne (voir section 4.1.1.1) :

1
2
			long firstColStencil(std::max(j - offsetStencilCol, 0l));
			long lastColStencil(std::min(j + offsetStencilCol + 1l, nbVecCol));


Définissons quelques variables temporaires (Les commentaires sont là pour rappeler la fonction scalaire) :

1
2
3
4
5
6
7
8
9
			long stencilIndexRow(0l);
			
// 			float u(matU[i*nbCol + j]), v(matV[i*nbCol + j]);
// 			float fullU(0.0f), fullV(0.0f);
			
			PRegVecf vecU(plib_load_ps(matVecVecU + i*padding + (i*nbVecCol + j)*PLIB_VECTOR_SIZE_FLOAT));
			PRegVecf vecV(plib_load_ps(matVecVecV + i*padding + (i*nbVecCol + j)*PLIB_VECTOR_SIZE_FLOAT));
			
			PRegVecf vecFullU(plib_broadcast_ss(0.0f)), vecFullV(plib_broadcast_ss(0.0f));


Nous devons maintenant boucler sur les lignes et les colonnes de notre stencil :

1
2
3
			for(long k(firstRowStencil); k < lastRowStencil; ++k){
				long stencilIndexCol(0l);
				for(long l(firstColStencil); l < lastColStencil; ++l){


Nous pouvons enfin calculer notre gradient. Les commentaires sont encore là pour aider, car l'utilisation de fonctions intrinsèques oblige à décomposer tous les calculs ce qui les rend difficiles à appréhender :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 					float deltaSquare(matDeltaSquare[stencilIndexRow*nbStencilCol + stencilIndexCol]);
					PRegVecf vecDeltaSquare(plib_load_ps(matBroadcastDeltaSquare +
									(stencilIndexRow*nbStencilCol + stencilIndexCol)*PLIB_VECTOR_SIZE_FLOAT));
					
					PRegVecf vecKLU(plib_load_ps(matVecVecU + k*padding + (k*nbVecCol + l)*PLIB_VECTOR_SIZE_FLOAT));
					PRegVecf vecKLV(plib_load_ps(matVecVecV + k*padding + (k*nbVecCol + l)*PLIB_VECTOR_SIZE_FLOAT));
					
					PRegVecf vecKLUminU(plib_sub_ps(vecKLU, vecU));
					PRegVecf vecKLVminV(plib_sub_ps(vecKLV, vecV));
					
					PRegVecf vecKLUminUdMultDeltaSquare(plib_mul_ps(vecKLUminU, vecDeltaSquare));
					PRegVecf vecKLVminVdMultDeltaSquare(plib_mul_ps(vecKLVminV, vecDeltaSquare));
					
					vecFullU = plib_add_ps(vecFullU, vecKLUminUdMultDeltaSquare);
					vecFullV = plib_add_ps(vecFullV, vecKLVminVdMultDeltaSquare);
					
// 					fullU += (matU[k*nbCol + l] - u)*deltaSquare;
// 					fullV += (matV[k*nbCol + l] - v)*deltaSquare;


Il ne faut pas oublier d'incrémenter les indices qui nous permettent de parcourir la matrice matDeltaSquare] :

1
2
3
4
					++stencilIndexCol;
				}
				++stencilIndexRow;
			}


On finalise le calcul :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 			float uvSquare(u*v*v);
			PRegVecf vecUVSquare(plib_mul_ps(vecU, plib_mul_ps(vecV, vecV)));
			
			PRegVecf vecOneMinusU(plib_sub_ps(vecOne, vecU));
			PRegVecf vecFeedPlusKill(plib_add_ps(vecFeedRate, vecKillRate));
			
			PRegVecf vecDiffFullU(plib_mul_ps(vecDiffudionRateU, vecFullU));
			PRegVecf vecDiffFullV(plib_mul_ps(vecDiffudionRateV, vecFullV));
			
			PRegVecf vecFeedRateMultOneMinusU(plib_mul_ps(vecFeedRate, vecOneMinusU));
			PRegVecf vecFeedPlusKillMultV(plib_mul_ps(vecFeedPlusKill, vecV));
			
			PRegVecf vecDiffFullUMinusUVSquare(plib_sub_ps(vecDiffFullU, vecUVSquare));
			PRegVecf vecDiffFullVPlusUVSquare(plib_add_ps(vecDiffFullV, vecUVSquare));
			
			PRegVecf vecDu(plib_add_ps(vecDiffFullUMinusUVSquare, vecFeedRateMultOneMinusU));
			PRegVecf vecDv(plib_sub_ps(vecDiffFullVPlusUVSquare, vecFeedPlusKillMultV));
			
// 			float du(diffudionRateU*fullU - uvSquare + feedRate*(1.0f - u));
// 			float dv(diffusionRateV*fullV + uvSquare - (feedRate + killRate)*v);


Et on sauvegarde le résultat :

1
2
3
4
5
6
7
8
9
10
11
			PRegVecf vecDuDt(plib_mul_ps(vecDu, vecDt));
			PRegVecf vecDvDt(plib_mul_ps(vecDv, vecDt));
			
			PRegVecf vecUPlusDuDt(plib_add_ps(vecU, vecDuDt));
			PRegVecf vecVPlusDvDt(plib_add_ps(vecV, vecDvDt));
			
			plib_store_ps(outMatVecU + i*padding + (i*nbVecCol + j)*PLIB_VECTOR_SIZE_FLOAT, vecUPlusDuDt);
			plib_store_ps(outMatVecV + i*padding + (i*nbVecCol + j)*PLIB_VECTOR_SIZE_FLOAT, vecVPlusDvDt);
			
// 			outMatVecU[i*nbCol + j] = u + du*dt;
// 			outMatVecV[i*nbCol + j] = v + dv*dt;


Fin des deux boucles sur les lignes et les colonnes :

1
2
		}
	}


Fin de la fonction :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
}

///Propagate the U and V species in the matVecVecU and matVecV for blocks
/**	@param[out] outMatVecU : updated matrix U version (with vectorial neighbours)
 * 	@param[out] outMatVecV : updated matrix V version (with vectorial neighbours)
 * 	@param matVecVecU : input of matrix U (with vectorial neighbours)
 * 	@param matVecV : input of matrix V (with vectorial neighbours)
 * 	@param matBroadcastDeltaSquare : matrix of the delta square values (with broadcast neighbours)
 * 	@param nbStencilRow : number of rows of the matrix matBroadcastDeltaSquare
 * 	@param nbStencilCol : number of columns of the matrix matBroadcastDeltaSquare
 * 	@param diffusionRateU : diffusion rate of the U specie
 * 	@param diffudionRateV : diffusion rate of the V specie
 * 	@param feedRate : rate of the process which feeds U and drains U, V and P
 * 	@param killRate : rate of the process which converts V into P
 * 	@param dt : time interval between two steps
*/
void grayscott_propagation_compute_link_block(PBlock<float> & outMatVecU, PBlock<float> & outMatVecV, const PBlock<float> & matVecVecU, const PBlock<float> & matVecVecV,
					const float * matBroadcastDeltaSquare, long nbStencilRow, long nbStencilCol,
					float diffusionRateU, float diffusionRateV, float feedRate, float killRate, float dt)
{
	size_t nbRow(outMatVecU.getFullNbRow()), nbCol(outMatVecU.getNbCol());
	grayscott_propagation_block(outMatVecU.getData(), outMatVecV.getData(), matVecVecU.getData(), matVecVecV.getData(), nbRow, nbCol,
				matBroadcastDeltaSquare, nbStencilRow, nbStencilCol,
				diffusionRateU, diffusionRateV, feedRate, killRate, dt, outMatVecU.getPadding());
}




Commençons avec la documentation (j'insisterai encore et toujours) suivit de la définition de notre fonction :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
///Propagate the U and V species in the matVecVecU and matVecV
/**	@param[out] vecBlockOutU : vector of output blocks for U
 * 	@param[out] vecBlockOutV : vector of output blocks for V
 * 	@param[out] vecBlockOutU : vector of input blocks for U
 * 	@param[out] vecBlockOutV : vector of input blocks for V
 * 	@param matBroadcastDeltaSquare : matrix of the delta square values (with broadcast neighbours)
 * 	@param nbStencilRow : number of rows of the matrix matBroadcastDeltaSquare
 * 	@param nbStencilCol : number of columns of the matrix matBroadcastDeltaSquare
 * 	@param diffusionRateU : diffusion rate of the U specie
 * 	@param diffudionRateV : diffusion rate of the V specie
 * 	@param feedRate : rate of the process which feeds U and drains U, V and P
 * 	@param killRate : rate of the process which converts V into P
 * 	@param dt : time interval between two steps
*/
void grayscott_propagation_link_block(std::vector<PBlock<float> > & vecBlockOutU, std::vector<PBlock<float> > & vecBlockOutV,
				std::vector<PBlock<float> > & vecBlockInU, std::vector<PBlock<float> > & vecBlockInV,
				const float * matBroadcastDeltaSquare, long nbStencilRow, long nbStencilCol,
				float diffusionRateU, float diffusionRateV, float feedRate, float killRate, float dt)
{
	std::vector<size_t> vecIndex;
	for(size_t i(0lu); i < vecBlockOutU.size(); ++i){vecIndex.push_back(i);}


Ici, on part dans le C++ 20 avec une fonction std::for_each qui permet d'exécuter une fonction lambda qui appelle notre calcul grayscott_propagation_compute_link_block sur tous nos blocs automatiquement.

1
2
3
4
5
6
7
	std::for_each(std::execution::seq, vecIndex.begin(), vecIndex.end(),
			[&](size_t i){
				grayscott_propagation_compute_link_block((PBlock<float>&)vecBlockOutU[i], (PBlock<float>&)vecBlockOutV[i],
								vecBlockInU[i], vecBlockInV[i],
								matBroadcastDeltaSquare, nbStencilRow, nbStencilCol,
								diffusionRateU, diffusionRateV, feedRate, killRate, dt);
			});


Note : on remarque la présence d'un & et non d'un = (dans les crochets, ligne 2) car le = copie des blocs. Comme ce n'est pas nécéssaire dans notre cas on utilise & car = nous ferait perdre du temps.


1
}