
Лекции / Лекция 10
.docТехнологии и методы программирования. Лекция 10.
Использование модулей.
Специфика MPI – все параллельные вычисления в одном модуле. Но это приводит к увеличению объёма кода на процессорах. Для борьбы с этим явлением применяются модули.
Пример:
MPI_Comm_rank(MPI_COMM_WORLD, *Proc_Rank);
if (Proc_Rank==0) DoMod0();
else if (Proc_Rank==1) DoMod1();
…
Такие стандартные структуры делают код более понятным.
Определение времени выполнения программы.
В стандарте MPI определены специальные функции определения времени выполнения. Их использование позволяет измерять время независимо от среды выполнения.
Прототип основной функции: double MPI_Wtime( void ). Измерение производится следующим образом: вычисляется время в начале выполнения операций и в конце, а потом одно вычитаем из другого. Другая функция - double MPI_Wtiсk( void ) - возвращает время между двумя отсчётами таймера.
Контроль выполнения программ.
Почти все функции MPI возвращают код завершения. По нему можно контролировать выполнение. MPI_SUCCESS – удачное выполнение, MPI_ERR – ошибка. Возможны варианты:
MPI_ERR_BUFFER – неверное использование буфера.
MPI_ERR_TRUNCATE – несоответствие размеров буферов отправителя и получателя
MPI_ERR_COMM – неверное использование коммуникатора
MPI_ERR_RANK – неверный ранг...
Все возможные ошибки описаны в спецификации и в mpi.h. При возникновении ошибок программа снимается с исполнения. Рекомендуется обрабатывать такие ситуации средствами MPI. Специально для этого существует MPI_About(MPI_COMM comm, int errcode). Можно указать код ошибки MPI_ERR_OTHER, тогда при любой ошибке программа завершится.
Простейшие коллективные операции передачи данных.
Один из ключевых моментов в MPI – синхронизация почти полностью за счёт сообщений. При этом передача данных может оказаться узким местом программы при распределённых вычислениях на кластере. Пример — вычисление суммы последовательности:
Схема: главный процесс инициирует последовательность в виде вектора и рассылает их по исполнителям. Процесс-исполнитель определяет интервал, для которого он вычисляет частичную сумму, а затем, вычислив её, отсылает результат главному процессу, на котором уже считается результат. Ключевой момент коммуникации — рассылка вектора. Это можно сделать и с помощью MPI_Send:
for (int i=0; i<Proc_Num-1; i++){
MPI_Send(&X, n, MPI_DOWBLE, I, 0, MPI_COMM_WORLD);
}
При этом присутствуют коммуникационные затраты: t(M)=tн+m*tк, где tк — передача 1 элемента, tн - время подготовки к передаче. Это повторится Proc_Num-1 раз, и столько же раз посчитается время подготовки. Гораздо лучше бы один раз подготовить сообщение. Это можно сделать с помощью коллективной рассылки: MPI_Bcast(void* buf, int count, MPI_Datatype datatype, int root, MPI_Comm comm), где root — ранг отправителя. Функция должна быть вызвана у всех процессоров одновременно, при этом процесс ранга root будет отправителем, а остальные — получателями.
Пример:
int main(int argc, char* argv[]){
double X[100];
int Proc_Num, Proc_Rank, n=100, k, I, i1, i2.
Double TotalSum=0, ProcSum=0;
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORD, *Proc_Num);
MPI_Comm_rank(MPI_COMM_WORLD, *Proc_Rank);
if (Proc_Rank==0) Init(X, n);
MPI_Bcast(&x, n, MPI_DOUBLE, 0, MPI_COMM_WORLD);
k=n/Proc_Num;
i1=k*Proc_Rank;
i2=k*(Proc_Rank+1)
if (Proc_Rank == Proc_Num-1) i2=n;
Это не лучший вариант — но самый простой. Лучше — распределить остаток равномерно, или, если если порядок получения детерминирован, передать остаток в первый процесс.
for(i=i1; i<i2; i++){
ProcSum+=X[i];
}
if (Proc_Rank==0){
TotalSum=ProcSum;
for (i=1; i<Proc_Num-1; i++){
MPI_Recv(&ProcSum, 1, MPI_DOUBLE, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &status);
TotalSum+=ProcSum;
}
} else{
MPI_Send(&ProcSum, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
}
if (Proc_Rank==0){
printf(TotalSum);
}
MPI_Finalize();
return 0;
Можно также использовать MPI_Reduce для сбора данных на одном процессоре: MPI_Reduce(void *sendbuf, void *recbuf, int count, MPI_Datatype type, MPI_Op op, int root, MPI_Comm comm), где root — получатель, а op определяет оператор, который будет выполняться каждый раз при сборе данных. Для определения оператора существуют предопределённые константы. К примеру:
MPI_SUMM - суммирование
MPI_PROP - умножение
MPI_MAX – выбор максимального
MPI_MIN - выбор минимального
Некоторые варианты могут отсутствовать в конкретной реализации MPI. Есть ограничения на типы данных.