![]() |
||||||||||||||||||||||||||||||||||
![]() |
||||||||||||||||||||||||||||||||||
![]() |
Инженерная методика адаптации приложения к гибридному кластеру с ускорителями на ПЛИСЧасть 4. Искусственное расширение теневой грани в локальной порции.С. С. Андреев, С. А. Дбар, А. О. Лацис, Е. А. Плоткина
Обратим внимание на то, что, при разбиении локальной порции на мелкие блоки, каждый из которых умещается в сопроцессоре, блоки эти придется нарезать с перекрытиями (теневыми гранями), подобно тому, как нарезается на локальные порции распределенный массив при распараллеливании приложения. Очевидно, что размер перекрытия при нарезке мелких блоков такой же, как и при нарезке локальных порций в параллельной реализации (в данном случае - одна строка). А что будет, если нарезать мелкие блоки с перекрытием в две строки? Каждый такой мелкий блок можно будет, единожды скопировав в сопроцессор, обработать два раза. Правда, при обратном копировании обработанных данных в память, придется "выбросить" области перекрытия удвоенного размера, но, по сравнению с двойной экономией на копировании данных, это не страшно. Легко показать, что при увеличении размера перекрытия в N раз по сравнению с требованиями исходного численного метода, единожды скопированный в сопроцессор мелкий блок может быть обработан внутри сопроцессора N раз. Значит, следующий шаг преобразования нашей программы - это включение в нее нарезки локальной порции на мелкие блоки с произвольной шириной перекрытия? Нет, к этому преобразованию переходить еще рано. Для того, чтобы многократная обработка мелких блоков данных была корректной, необходимо, чтобы была корректной многократная (без обменов краями с соседями) обработка всей локальной порции. Для этого, в свою очередь, потребуется нарезать уже сами локальные порции с расширенными в N раз перекрытиями, а обмены такими расширенными краями сделать в N раз более редкими. С этого преобразования программы и начнем. Текст преобразованной таким образом программы приводится ниже. Текст функции main(): #include <mpi.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "vsda.h" #include "dimension.h" // Change here the number of steps, the cell geometry, etc #define STEPITER 1000 #define delx 0.5 #define dely 0.25 #define NITER 10002 #define STW 3 // end change here. /***/ extern void itstep( int mx, int my, void *f, void *newf, void *r, double rdx2, double rdy2, double beta, int niters ); int main( int argc, char **argv ) { int i, j, k, n, mx1, mx2, my1, my_number, n_of_nodes, totalmx, partmx, leftmx, mx, my, niter, stw; FILE *fp; double howlong; double rdx2, rdy2, beta; VSDA vsda; long *distr_out; MPI_Request rq[5]; /***/ MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &my_number ); MPI_Comm_size( MPI_COMM_WORLD, &n_of_nodes ); if ( argc != 3 ) { if ( !my_number ) { fprintf( stderr, "Usage: %s <nrows> <ncolumns>\n", argv[0] ); fflush( stderr ); } return (-1); } totalmx = mx = (int)atol( argv[1] ); my = (int)atol( argv[2] ); if ( my < 1 ) { if ( !my_number ) { fprintf( stderr, "Number of columns (%d) should be positive\n", my ); fflush( stderr ); } return (-1); } distr_out = (long*)malloc( n_of_nodes*sizeof( *distr_out ) ); if ( !distr_out ) { fprintf( stderr, "No memory\n" ); fflush( stderr ); return( -1 ); } totalmx += 2; my += 2; stw = STW; niter = NITER; /* Make STW odd, NITER divisible by STW: */ if ( !(stw%2) ) stw--; niter -= (niter%stw); /* Here we know the array width, so make the arrays themselves: */ { DIM2( double, f, my ); DIM2( double, newf, my ); DIM2( double, r, my ); void *tmpf; /* Make the distributed array, discover the local portion length: */ VSDA_Create( MPI_COMM_WORLD, VSDA_BLOCKED, 0, (long)totalmx, (long)sizeof(*f), (long)stw, NULL, distr_out, &vsda ); mx = distr_out[my_number]; if ( n_of_nodes > 1 ) { if ( my_number > 0 ) mx += stw; if ( my_number < (n_of_nodes-1) ) mx += stw; } f = (typeof(f))malloc( mx*sizeof(*f) ); newf = (typeof(newf))malloc( mx*sizeof(*newf) ); r = (typeof(r))malloc( mx*sizeof(*r) ); if ( (!f) || (!newf) || (!r) ) { fprintf( stderr, "Cannot allocate, exiting\n" ); exit( -1 ); } rdx2 = 1./delx/delx; rdy2 = 1./dely/dely; beta = 1.0/(2.0*(rdx2+rdy2)); if ( !my_number ) { printf( "Solving heat conduction task on %d by %d grid by %d processors\n", totalmx-2, my-2, n_of_nodes ); fflush( stdout ); } for ( i = 0; i < mx; i++ ) { for ( j = 0; j < my; j++ ) { if ( ((i==0)&&(my_number==0)) || (j==0) || ((i==(mx-1))&&(my_number==(n_of_nodes-1))) || (j==(my-1)) ) newf[i][j] = f[i][j] = 1.0; else newf[i][j] = f[i][j] = 0.0; r[i][j] = 0.0; } } /* Iteration loop: */ howlong = MPI_Wtime(); for ( n = 0; n < niter/stw; n++ ) { if ( !my_number ) { if ( !(n%STEPITER) ) printf( "Iteration %d\n", n ); } /* Step of calculation starts here: */ itstep( mx, my, f, newf, r, rdx2, rdy2, beta, stw ); /* Do all the transfers: */ VSDA_Update_begin( &vsda, rq, newf ); VSDA_Update_end( rq ); /* swap the halves: */ tmpf = newf; newf = f; f = (typeof(f))tmpf; } if ( !my_number ) { printf( "Elapsed time: %f sec\n", (float)(MPI_Wtime()-howlong) ); fp = fopen( "output_par_2.dat", "w" ); fclose( fp ); } for ( j = 0; j < n_of_nodes; j++ ) { MPI_Barrier( MPI_COMM_WORLD ); if ( j == my_number ) { int istart, isize; istart = 1; isize = mx-1; if ( n_of_nodes > 1 ) { if ( my_number > 0 ) istart = stw; if ( my_number < (n_of_nodes-1) ) isize = mx-stw; } fp = fopen( "output_par_2.dat", "a" ); for ( i = istart; i < isize; i++ ) { fwrite( &(f[i][1]), my-2, sizeof(f[0][0]), fp ); } fclose( fp ); } } } MPI_Finalize(); return 0; } Обратим внимание на то, что не только усложнять, но и вообще как-либо изменять ту часть логики программы, которая организует обмены данными между MPI - процессами, нам не пришлось, что свидетельствует о действительной пригодности библиотеки VSDA для упрощения разработки задач этого класса. Текст функции itstep(): #include "dimension.h" void itstep( int mx, int my, void *pf, void *pnewf, void *pr, double rdx2, double rdy2, double beta, int niter ) { DIM2( double, f, my ) = (typeof(f))pf; DIM2( double, newf, my ) = (typeof(newf))pnewf; DIM2( double, r, my ) = (typeof(r))pr; int i, j, k, mx1, my1; void *tmpf; /***/ mx1 = mx - 1; my1 = my - 1; for ( k = 0; k < niter; k++ ) { for ( i = 1; i < mx1; i++ ) { for ( j = 1; j < my1; j++ ) { newf[i][j] = ((f[i-1][j]+f[i+1][j])*rdx2+(f[i][j-1]+f[i][j+1])*rdy2-r[i][j])*beta; } } tmpf = newf; newf = f; f = (typeof(f))tmpf; } } Приведем необходимые пояснения. Коэффициент расширения теневых граней задан константой STW (STencil Width, ширина шаблона) в тексте функции main(). Чтобы избежать еще большего усложнения программы, нам пришлось наложить довольно противоестественные ограничения на исходные данные. Мы требуем, чтобы число итераций было кратно STW, и чтобы само STW было нечетным. Последнее требование связано с тем, что и в функции main(), и в вызываемой из нее функции itstep(), независимо друг от друга меняются местами указатели f и newf. Для корректной работы программы необходимо, чтобы они менялись местами согласованно, то есть чтобы после вызова itstep() результаты счета были в том массиве, на которую в вызывающей программе указывает newf. А для этого необходимо и достаточно, чтобы число замен местами f и newf, которое равно STW, было нечетным. ◄ Часть 3 Часть 5 ► |
![]() |
||||||||||||||||||||||||||||||||
Тел. +7(499)220-79-72; E-mail: inform@kiam.ru |