Математическое моделирование в астрофизике. В 2 частях. Ч.2. Учебное пособие
.pdfint source , MPI_Comm comm );
Значения count, datatype, source, comm должны быть одинаковы у всех процессов.
2. Cборка данных со всех процессов в буфере rbuf процесса dest (в порядке возрастания номера процесса):
MPI_Gather ( void * sbuf , int scount , MPI_Datatype stype , void * rbuf , int rcount , MPI_Datatype rtype , int
dest , MPI_Comm comm );
3. обратная операция (рассылка данных от процесса source всем остальным):
MPI_Scatter ( void * sbuf , int scount , MPI_Datatype stype , void * rbuf , int rcount , MPI_Datatype rtype , int source , MPI_Comm comm );
Все вышеперечисленные операции осуществляются с блокировкой. Пример применения операции MPI_Bcast приведен в листинге 4.13, а примеры применения операций MPI_Gather и MPI_Scatter – в листингах 4.14 и 4.15. В листинге 4.14 массивы sendarray посылаются процессу root всеми процессами, в том числе самим root, и объединяются в массиве rbuf. Память под массив rbuf выделяется только в принимающем процессе. В листинге 4.15 процесс root раздает всем процессам, включая себя, по сто элементов массива sendbuf.
Листинг 4.13. Программа с использованием процедуры MPI_Bcast
для рассылки текстового сообщения всем процессам от процесса
|
|
с номером 1 |
1 |
# include |
" mpi .h" |
2 |
# include |
< stdio .h > |
3 |
# include |
< string .h > |
4 |
|
|
5 |
int main ( int argc , char ** argv ) |
|
6 |
{ |
|
81
7
8
9
int numtasks , rank , root ;
char sbuf [20];
strcpy (sbuf , "I am not root \0");
10 root = 1;
11
12MPI_Init (& argc , & argv );
13MPI_Comm_size ( MPI_COMM_WORLD , & numtasks );
14MPI_Comm_rank ( MPI_COMM_WORLD , & rank );
15
16if ( rank == root )
17strcpy (sbuf , " Hello from root \0");
18MPI_Bcast (sbuf , 16, MPI_CHAR , root , MPI_COMM_WORLD );
19
20if ( rank == root )
21strcpy (sbuf , "I am root \0");
22printf ("I am %d. Message received : %s\n", rank , sbuf );
23
24 |
MPI_Finalize (); |
|
25 |
return 0; |
|
26 |
} |
|
|
Листинг 4.14. Фрагмент программы с использованием |
|
|
|
процедуры MPI_Gather |
1 |
MPI_Comm comm ; |
|
2 |
int |
gsize , sendarray [100]; |
3 |
int |
root ; |
4 |
int * rbuf ; |
|
5 ... |
|
|
6 |
MPI_Comm_rank (comm , & myrank ); |
|
7 |
if |
( myrank == root ) |
8 |
{ |
|
82
9MPI_Comm_size ( comm , & gsize );
10 |
rbuf = new int [ gsize * 100]; |
11 |
} |
12 |
MPI_Gather ( sendarray , 100 , MPI_INT , rbuf , 100 , |
|
MPI_INT , root , comm ); |
Листинг 4.15. Фрагмент программа с использованием процедуры MPI_Scatter
1 |
MPI_Comm comm ; |
|
2 |
int |
gsize ; |
3 |
int * |
sendbuf ; |
4 |
int |
root , rbuf [100]; |
5...
6 |
MPI_Comm_rank ( comm , & myrank ); |
7 |
if ( myrank == root ) |
8 |
{ |
9MPI_Comm_size ( comm , & gsize );
10 sendbuf = new int [ gsize * 100];
11}
12...
13MPI_Scatter ( sendbuf , 100 , MPI_INT , rbuf , 100 , MPI_INT , root , comm );
Следует упомянуть о типах данных для пересылки. Существуют встроенные типы MPI_INT, MPI_FLOAT, MPI_DOUBLE, MPI_CHAR и т. д., однако на их основе разработчик может конструировать свои собственные. Производные типы данных являются структурами, состоящими из основных типов, например смежный тип:
MPI_Type_contiguous ( int count , MPI_Datatype oldtype , MPI_Datatype * newtype );
Эта процедура создает из типа oldtype тип newtype, объединяя count
элементов этого типа, что удобно при пересылке участков массивов, хранящихся в памяти последовательно.
83
Можно создать и более сложный тип данных – маску, позволяющую пересылать элементы массива не подряд, а с некоторым интервалом между ними (например, только нечетные):
int MPI_Type_vector ( int count , int blocklength , int stride , MPI_Datatype oldtype , MPI_Datatype * newtype );
Данная процедура создает тип для пересылки count участков массива, причем количество элементов каждого пересылаемого участка равно blocklength, а количество элементов между началами участков равно
stride.
Следующая процедура делает маску еще сложнее: теперь длина каждого участка и расстояние между ними задаются индивидуально при помощи массивов array_of_blocklengths и array_of_displacements:
int MPI_Type_indexed ( int count , int *
array_of_blocklengths , int * array_of_displacements , MPI_Datatype oldtype , MPI_Datatype * newtype );
Структурный тип объединяет для пересылки элементы различных ти-
пов:
int MPI_Type_struct ( int count , int * array_of_blocklengths , MPI_Aint *
array_of_displacements , MPI_Datatype * array_of_types , MPI_Datatype * newtype );
Пример создания и использования типа для пересылки частиц в кинетическом коде с использованием структурного типа приведен в листинге 4.16.
Листинг 4.16. Фрагмент программы с созданием типа данных
для пересылки частиц в кинетическом коде
1 // special structure for sending particle data
2 typedef struct tagSENDPART
3 {
4 int sort ; // sort of particle
84
5 |
double |
v [3]; |
// velocity |
|
6 |
double |
r [3]; |
// position |
in space |
7 |
} SENDPART ; |
|
|
|
8 |
|
|
|
|
9 |
void Build_MPI_Type_SENDPART ( MPI_Datatype * newtype ) |
|||
10{
11SENDPART * p = new SENDPART ;
12int count = 3;
13int blocklengths [3];
14MPI_Aint displacements [3];
15MPI_Aint addresses [4];
16MPI_Datatype types [3];
17 |
types [0] |
= |
MPI_INT ; // sort |
|||
18 |
types [1] |
= |
MPI_DOUBLE ; |
// v |
||
19 |
types [2] |
= |
MPI_DOUBLE ; |
// r |
||
20 |
blocklengths [0] |
= |
1; |
|
||
21 |
blocklengths [1] |
= |
3; |
|
||
22 |
blocklengths [2] |
= |
3; |
|
||
23 |
MPI_Address (p , & addresses [0]) ; |
|
|
|||
24 |
MPI_Address (&(p -> sort ) , & addresses [1]) ; |
|||||
25 |
MPI_Address (&(p ->v) , |
& addresses [2]) ; |
||||
26 |
MPI_Address (&(p ->r) , |
& addresses [3]) ; |
||||
27 |
displacements [0] |
= |
addresses [1] |
- |
addresses [0]; |
|
28 |
displacements [1] |
= |
addresses [2] |
- |
addresses [0]; |
|
29 |
displacements [2] |
= |
addresses [3] |
- |
addresses [0]; |
|
30MPI_Type_struct ( count , blocklengths , displacements , types , newtype );
31MPI_Type_commit ( newtype );
32delete p;
33}
34...
35 int main ( int argc , char ** argv )
36 {
85
37...
38MPI_Datatype MPI_SENDPART ;
39Build_MPI_Type_SENDPART (& MPI_SENDPART );
40...
41 MPIsend ( parts , 4, MPI_SENDPART , dest , tag ,
MPI_COMM_WORLD );
42...
43MPI_Type_free ( MPI_SENDPART );
44...
45}
Вкодах с пространственной дискретизацией при распараллеливании часто создаются дополнительные ячейки для пересылок (ghost cells) (рис. 4.5). При вычислении градиентов эти ячейки соседствуют с граничными ячейками. В дополнительные ячейки периодически присылается актуальная информация о полях, плотностях и токах в граничных ячейках соседнего процесса.
Во многих случаях технологии векторизации, OpenMP и MPI можно совмещать, т. е. векторизовать операции над массивами, распараллеливать с помощью OpenMP внешние циклы и осуществлять коммуникацию между узлами при помощи технологии MPI. Число ядер в современных вычислительных комплексах может достигать сотен тысяч. Благодаря этому техно-
Рис. 4.5. Дополнительные ячейки для пересылок в кодах с пространственной дискретизацией
86
логии параллельного программирования позволяют решать задачи, с которыми не может справиться ни один персональный компьютер.
Упражнения
1. Оптимизировать программу, считающую ускорение в магнитном поле, энергию и импульс массива частиц. С помощью библиотеки time.h определить время работы программы до и после оптимизации, усредненное по нескольким запускам.
Текст программы:
1 |
# include |
|
" time .h" |
|
|
|
||
2 |
# include |
|
" math .h" |
|
|
|
||
3 |
# include |
|
" stdio .h" |
|
|
|
||
4 |
|
|
|
|
|
|
|
|
5 |
int NP |
= |
|
1000; |
|
|
|
|
6 |
double |
c |
|
= |
3 e10 ; |
|
|
|
7 |
double mp = 1.67 e -24; |
|
|
|||||
8 |
double me = 9.1e -28; |
|
|
|
||||
9 |
double q = 4.8e -10; |
|
|
|
||||
10 |
double * |
|
v |
= |
new double [ NP ]; |
// velocity |
||
11 |
double * |
|
a |
= |
new double [ NP ]; |
// acceleration |
||
12 |
double * |
|
E |
= |
new double [ NP ]; |
// energy |
||
13 |
double * |
|
p |
= |
new double [ NP ]; |
// momentum |
||
14 |
double |
Vmax |
= 1 e8 ; |
// max |
velocity |
|||
15 |
|
|
|
|
|
|
|
|
16 |
double GetRand ( double min , |
|
double max ) |
|||||
17 |
{ |
|
|
|
|
|
|
|
18 |
return |
|
( min + ( max |
- min ) |
* rand () / ( RAND_MAX + |
|||
|
1.0) ); |
|
|
|
|
|||
19 |
} |
|
|
|
|
|
|
|
20 |
|
|
|
|
|
|
|
|
21 |
int main ( int |
argc , char ** |
argv ) |
|||||
22 |
{ |
|
|
|
|
|
|
|
87
23double worktime ;
24// initialization
25 |
for |
( int |
i |
= |
0; |
i |
< |
NP ; i ++) |
|
26 |
{ |
|
|
|
|
|
|
|
|
27 |
v[i] = |
GetRand (0 , |
Vmax ); |
||||||
28 |
} |
|
|
|
|
|
|
|
|
29 |
// optimize |
only |
this |
|
cycle ! |
|
|||
30 |
for |
( int |
i |
= |
0; |
i |
< |
NP ; i ++) |
|
31 |
{ |
|
|
|
|
|
|
|
|
32 |
if |
(i % |
2 |
== |
0) |
|
|
|
|
33 |
{ |
|
|
|
|
|
|
|
|
34 |
|
a[i] = q * v[i] * mp / c; |
|||||||
35 |
|
E[i] |
= |
mp * pow (v[i], |
2) / 2; |
||||
36 |
|
p[i] |
= |
sqrt (2 |
* |
mp * |
E[i ]) ; |
||
37}
38else
39{
40
41
42
43}
44}
45printf (" total work time = %f seconds \n", worktime );
46}
2.Откомпилировать программу с флагом векторизации. Выяснить, векторизуется ли цикл. Если нет – исправить ситуацию (возможно, с применением директив OpenMP). С помощью библиотеки time.h определить время работы программы с векторизацией и без.
|
Текст программы: |
|
1 |
# include " time .h" |
|
2 |
# include |
" math .h" |
3 |
# include |
" stdio .h" |
88
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int N |
= 100000; |
|
|
|
|
|
|
||
double |
func ( double |
x , |
double |
y , double |
z) |
||||
{ |
|
|
|
|
|
|
|
|
|
if (z > 0) |
|
|
|
|
|
|
|
||
return x * x + sqrt (y); |
|
|
|||||||
else |
|
|
|
|
|
|
|
|
|
return sin (x) |
+ sqrt (y); |
|
|
||||||
} |
|
|
|
|
|
|
|
|
|
int main ( int argc , |
char ** argv ) |
|
|||||||
{ |
|
|
|
|
|
|
|
|
|
double worktime ; |
|
|
|
|
|
||||
double * A = new double [N ]; |
|
|
|||||||
double * B = new double [N ]; |
|
|
|||||||
for |
( int |
i |
= |
0; |
i < |
N; |
i ++) |
|
|
A[i] = double (i); |
|
|
|
|
|||||
for |
( int |
i |
= |
0; |
i < |
N; |
i ++) |
|
|
{ |
|
|
|
|
|
|
|
|
|
if |
(( i |
!= |
N |
- |
1) |
&& |
(i != 0) ) |
|
|
|
B[i] |
= |
func (A[i], |
A[i + |
1] , A[i |
- 1]) ; |
|||
26}
27printf (" total work time = %f seconds \n", worktime );
28}
3.Распараллелить методом OpenMP программу подсчета суммарной энергии массива частиц. С помощью библиотеки time.h определить время счета до и после распараллеливания.
Текст программы:
#include " time .h"
#include " math .h"
#include " stdio .h"
89
int NP = 100000; |
|
|
|
|
double GetRand ( double min , |
double max ) |
|||
{ |
|
|
|
|
return ( min |
+ ( max |
- |
min ) * rand () / ( RAND_MAX + |
|
1.0) ); |
|
|
|
|
} |
|
|
|
|
int main ( int |
argc , char ** |
argv ) |
||
{ |
|
|
|
|
double worktime ; |
|
|
|
|
double m2 = 0.5; |
|
|
|
|
double v = 0.0; |
|
|
|
|
double E = 0.0; |
|
|
|
|
double Vmax = 1.0; |
|
|
|
|
for ( int i |
= 0; i |
< |
NP ; |
i ++) |
{ |
|
|
|
|
v = GetRand ( Vmax ); |
|
|
||
E += m2 * |
v * v; |
|
|
|
}
printf ("E = %f work time = %f seconds \n", E, worktime );
}
4. Распараллелить методом OpenMP и векторизовать программу суммирования элементов массива. С помощью библиотеки time.h определить время счета до и после распараллеливания.
Текст программы:
#include " time .h"
#include " math .h"
#include " stdio .h"
90
