МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ИМЕНИ М. В. ЛОМОНОСОВА ФАКУЛЬТЕТ ВЫЧИСЛИТЕЛЬНОЙ МАТЕМАТИКИ И КИБЕРНЕТИКИ Отчет по практическому заданию «Разработка параллельной программы и исследование ее эффективности» Выполнил студент Туренко А. С. Преподаватель Кузина Л. Н. Москва, 2014 Содержание 1 Постановка задачи 2 2 Результаты 2 3 Исходный код 2 4 Выводы 6 1 Постановка задачи Дана последовательная программа, реализующая алгоритм релаксации Якоби. Требуется разработать параллельную программу с использованием технологии OpenMP и провести исследование ее эффективности. В частности, исследовать время выполнения разработанной программы в зависимости от размера сетки и количества используемых потоков на вычислительном комплексе IBM Regatta. 2 Результаты Программа компилировалась со следующими параметрами (пример для сетки 256x256x256): $ gcc -Wall -Wextra -std=c99 -pedantic -O2 -DN=256 -fopenmp -o lab1_1-256 lab1_1.c Для последовательной версии (без использования OpenMP): $ gcc -Wall -Wextra -std=c99 -pedantic -O2 -DN=256 -o lab1_1-256-noomp lab1_1.c Для сеток 128x128x128 и 384x384x384 параметры аналогичны. Результаты: Размер сетки Последовательный алгоритм, сек 128x128x128 256x256x256 384x384x384 10,8669 109,3690 331,6430 1 процессор Время Уск. 11,5534 0,9406 111,6110 0,9799 349,1800 0,9498 Параллельный 2 процессора Время Уск. 5,7421 1,8925 58,4983 1,8696 183,1470 1,8108 алгоритм, сек 4 процессора Время Уск. 3,2603 3,3331 34,8567 3,1377 104,7010 3,1675 1 процессор Время Уск. 10,0709 0,8314 104,3670 0,8732 313,2430 1,0033 Параллельный алгоритм, сек 2 процессора 4 процессора Время Уск. Время Уск. 4,8606 1,7226 2,6187 3,1973 51,9095 1,7555 27,0217 3,3724 154,0950 2,0394 81,4303 3,8593 8 процессоров Время Уск. 2,3134 4,6974 20,9346 5,2243 67,3500 4,9242 Для версии с -O3 вместо -O2: Размер сетки Последовательный алгоритм, сек 128x128x128 256x256x256 384x384x384 8,3730 91,1276 314,2640 8 процессоров Время Уск. 1,3989 5,9855 13,5873 6,7068 44,7457 7,0233 Графики зависимости ускорения от количества процессоров по данным из этих таблиц (для O2 и -O3) приведены ниже на отдельной странице. Красным цветом обозначен график для сетки 128x128x128, зеленым — 256x256x256, черным — 384x384x384. График y=x показывает максимально достижимое в теории ускорение (при отсутствии накладных расходов на распараллеливание). 3 Исходный код #include <math.h> #include <stdlib.h> #include <stdio.h> #ifdef _OPENMP #include <omp.h> #else #include <sys/time.h> #endif #define MAX(a,b) ((a)>(b)?(a):(b)) #ifndef N #define N 256 #endif #define NN ((N)*(N)) #define NNN ((NN)*(N)) #ifndef MAX_THREADS_CNT #define MAX_THREADS_CNT 8 #endif static const double maxeps = 0.1e-7; 8 Speedup 6 4 2 0 0 2 4 CPUs 6 8 Рис. 1: График зависимости укорения от количества процессоров для -O2 8 Speedup 6 4 2 0 0 2 4 CPUs 6 8 Рис. 2: График зависимости укорения от количества процессоров для -O3 static const int itmax = 100; double eps; double data1[NNN], data2[NNN]; double *A, *B; double m[MAX_THREADS_CNT]; void void void void void relax(); resid(); init(); verify(); wtime(double *t); int main() { double time0, time1; /* omp_set_num_threads(MAX_THREADS_CNT); */ A = data1; B = data2; init(); wtime(&time0); for (int it = 1; it <= itmax; ++it) { eps = 0.0; relax(); resid(); printf("it=%4i eps=%f\n", it, eps); if (eps < maxeps) break; } wtime(&time1); printf("Time in seconds=%gs\t", time1 - time0); verify(); return 0; } void init() { for (int i = 0; i <= N-1; ++i) for (int j = 0; j <= N-1; ++j) for (int k = 0; k <= N-1; ++k) if (i == 0 || i == N-1 || j == 0 || j == N-1 || k == 0 || k == N-1) { A[i*NN + j*N + k] = 0.0; } else { A[i*NN + j*N + k] = (4.0 + i + j + k); } } void relax() { #pragma omp parallel for schedule(dynamic) for (int i = 1; i <= N-2; ++i) for (int j = 1; j <= N-2; ++j) for (int k = 1; k <= N-2; ++k) { int idx = i*NN + j*N + k; B[idx] = ( A[idx - NN] + A[idx + NN] + A[idx A[idx A[idx A[idx + + N N 1 1 ] + ] + ] + ]) / 6.0; } } #ifndef _OPENMP void resid() { double *T; for (int i = 1; i <= N-2; ++i) for (int j = 1; j <= N-2; ++j) for (int k = 1; k <= N-2; ++k) { int idx = i*NN + j*N + k; double e = fabs(A[idx] - B[idx]); eps = MAX(eps, e); } T = A; A = B; B = T; } #else void resid() { double *T; for (int t = 0; t < MAX_THREADS_CNT; ++t) m[t] = 0.0; #pragma omp parallel for schedule(static) for (int i = 1; i <= N-2; ++i) for (int j = 1; j <= N-2; ++j) for (int k = 1; k <= N-2; ++k) { int t = omp_get_thread_num(); int idx = i*NN + j*N + k; double e = fabs(A[idx] - B[idx]); m[t] = MAX(m[t], e); } for (int t = 0; t < MAX_THREADS_CNT; ++t) { eps = MAX(eps, m[t]); } T = A; A = B; B = T; } #endif void verify() { double s; s = 0.0; for (int i = 0; i <= N-1; ++i) for (int j = 0; j <= N-1; ++j) for (int k = 0; k <= N-1; ++k) s = s + A[i*NN + j*N + k] * (i+1) * (j+1) * (k+1) / (NNN); printf(" S = %f\n", s); } #ifndef _OPENMP void wtime(double *t) { static int sec = -1; struct timeval tv; gettimeofday(&tv, (void *)0); if (sec < 0) sec = tv.tv_sec; *t = (tv.tv_sec - sec) + 1.0e-6 * tv.tv_usec; } #else void wtime(double *t) { *t = omp_get_wtime(); } #endif 4 Выводы Видно, что скомпилированная с поддержкой OpenMP программа, запускаемая на одном процессоре, в основном проигрывает последовательной. Это объясняется различием в вычислении максимума в параллельной и последовательной версии функции resid(): параллельная версия вычисляет M максимумов, где M — число потоков, вычисляя затем максимум среди них, что ведет к дополнительным накладным расходам. Ускорение зависит от количества процессоров не совсем пропорционально, что, учитывая накладные расходы на поддержание параллелизма и синхронизацию, и ожидалось. Ускорение увеличивается на величину, немного меньшую двойки, при удвоении числа процессоров и неизменности параметров компиляции и алгоритма.