Double MPI_Wtick(void);
позволяющая определить время в секундах между двумя последовательными показателями времени аппаратного таймера используемой компьютерной системы.
Рассмотрим пример программы, использующей функцию MPI_Wtime(). Программа выполняет поиск суммы элементов одномерного массива и использует следующий порядок действий:
1. Процесс с рангом 0 запрашивает с клавиатуры размер массива, после чего динамически выделяет память под массив и заполняет его случайными числами.
2. Процесс 0 передает процессу 1 размер массива.
3. Процесс 0 передает процессу 1 первую половину массива.
4. Процесс 0 вычисляет сумму оставшейся половины массива и ждет ответ от процесса 1.
5. Процесс 1 получает от процесса 0 размер массива и динамически выделяет память под половину этого размера.
6. Процесс 1 получает от процесса 0 массив данных.
7. Процесс 1 вычисляет сумму элементов массива, отсылает ее процессу 0 и завершает работу.
8. Процесс 0 принимает от процесса 1 значение суммы первой половины массива, складывает с суммой второй половины массива, выводит результат на экран и завершает работу.
Сначала приведем листинг программы без использования функции MPI_Wtime():
#include "stdafx.h"
#include <stdio.h>
#include <conio.h>
#include <mpi.h>
#include <iostream>
Using namespace std;
int main(int argc, char* argv[])
{
Int proc_rank, proc_count;
MPI_Status Status;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &proc_count);
MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank);
//===================================================================
if ((proc_rank==0) && (proc_count>1)) // Если это "главный" процесс
{ // и процессов минимум два
int n; // Размер массива (изначально неизвестен)
cout << "Input n: ";
cin >> n; // Вводим с клавиатуры размер массива
// Заводим массив на n элементов и заполняем его случайными числами:
int *mas; // Указатель на массив типа int
mas = new int [n]; // Выделяем память под массив (без проверки)
for (int i=0; i<n; i++) // и заполняем случайными числами от 0 до 10
mas[i]=rand()%11;
// Распараллелим поиск суммы ровно на два процесса.
// Даже если доступных процессорных ядер будет больше,
// они не будут использоваться. Ровно два процесса:
// текущий процесс с рангом 0 и процесс с рангом 1.
// Поскольку процесс 1 не знает о размере массива, он
// не может знать, какой объем сообщения ему принимать.
// Поэтому мы сначала передадим процессу 1 число, равное
// размеру массива. Так процесс 1 сможет подготовить
// память под принимаемый массив.
MPI_Send (&n, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
// Теперь отсылаем первую половину массива процессу 1
MPI_Send (mas, n/2, MPI_INT, 1, 0, MPI_COMM_WORLD);
// Пока процесс 1 обрабатывает первую половину массива,
// делаем пока свою часть работы: ищем сумму элементов
// оставшейся части массива
int S=0;
for (int i=n/2; i<n; i++)
S=S+mas[i];
// Готовимся получать результат работы процесса 1.
// Результатом будет одно число типа int.
int S1; // Место под результат
// Ждем сообщение от процесса 1
MPI_Recv (&S1, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &Status);
S=S+S1; // Складываем обе суммы
cout << "S = " << S << endl;
delete [] mas; // Освобождаем память, выделенную под массив
} // if - процесс 0
//===================================================================
if (proc_rank==1) // Если это процесс с номером 1
{
int size; // Размер принимаемого массива
int Summa; // Сумма его элементов
// Готовимся принять размер массива
// Ждем сообщение от процесса 0
MPI_Recv (&size, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &Status);
size=size/2; // Мы получили размер всего массива,
// но принимать будем только половину
// Динамически выделяем память под принимаемый массив
int *ptr = new int [size];
// Готовимся принять массив данных
// Ждем сообщение от процесса 0
MPI_Recv (ptr, size, MPI_INT, 0, 0, MPI_COMM_WORLD, &Status);
// Ищем сумму элементов массива
Summa=0;
for (int i=0; i<size; i++)
Summa+=ptr[i];
// Отсылаем ответ процессу 0
MPI_Send (&Summa, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
delete [] ptr; // Освобождаем память
} // if - процесс 1
//===================================================================
MPI_Finalize();
// закрываем MPI-библиотеку
Return 0;
}
Пример результата работы программы:
Input n: 1000
S = 5175
Рассмотренная программа полностью работоспособна и решает поставленную задачу. Однако данный код не позволяет отследить и проанализировать процессы, вызванные распараллеливанием программы. Во многих случаях, особенно на этапе отладки программы, в ее листинг полезно добавлять команды вывода сведений о выполняемых этапах работы и промежуточных результатах. Ниже приводится пример этой же программы с добавленными командами вывода информационных сообщений.
#include "stdafx.h"
#include <stdio.h>
#include <conio.h>
#include <mpi.h>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
int proc_rank, proc_count;
MPI_Status Status;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &proc_count);
MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank);
double t0, t1, t2, t3, t4; // Переменные для измерения времени
t0=MPI_Wtime(); // Запоминаем начальный момент времени
//===================================================================
if ((proc_rank==0) && (proc_count>1)) // Если это "главный" процесс
{ // и процессов минимум два
int n; // Размер массива (изначально неизвестен)
cout << "Input n: ";
cin >> n; // Вводим с клавиатуры размер массива
t1 = MPI_Wtime(); // Запоминаем момент времени до начала работы
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): started.\n";
// Заводим массив на n элементов и заполняем его случайными числами:
int *mas; // Указатель на массив типа int
mas = new int [n]; // Выделяем память под массив (без проверки)
for (int i=0; i<n; i++) // и заполняем случайными числами от 0 до 10
mas[i]=rand()%11;
// Распараллелим поиск суммы ровно на два процесса.
// Даже если доступных процессорных ядер будет больше,
// они не будут использоваться. Ровно два процесса:
// текущий процесс с рангом 0 и процесс с рангом 1.
// Поскольку процесс 1 не знает о размере массива, он
// не может знать, какой объем сообщения ему принимать.
// Поэтому мы сначала передадим процессу 1 число, равное
// размеру массива. Так процесс 1 сможет подготовить
// память под принимаемый массив.
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): sanding size...\n";
MPI_Send (&n, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
cout <<"Process 0 ("<< MPI_Wtime()-t0 << "): size=" << n << " sended.\n";
// Теперь отсылаем первую половину массива процессу 1
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): sanding array...\n";
MPI_Send (mas, n/2, MPI_INT, 1, 0, MPI_COMM_WORLD);
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): array sended.\n";
// Пока процесс 1 обрабатывает первую половину массива,
// делаем пока свою часть работы: ищем сумму элементов
// оставшейся части массива
int S=0;
for (int i=n/2; i<n; i++)
S=S+mas[i];
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): local summa calculated, S=" << S << ".\n";
// Готовимся получать результат работы процесса 1.
// Результатом будет одно число типа int.
int S1; // Место под результат
// Ждем сообщение от процесса 1
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): waiting answer from process 1...\n";
MPI_Recv (&S1, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &Status);
cout <<"Process 0 ("<< MPI_Wtime()-t0 << "): answer received, S1=" << S1 << ".\n";
S=S+S1; // Складываем обе суммы
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): S = " << S << endl;
delete [] mas; // Освобождаем память, выделенную под массив
cout << "Process 0 ("<< MPI_Wtime()-t0 << "): finished.\n";
t2 = MPI_Wtime(); // Запоминаем момент времени по окончании работы
cout <<"Process 0 ("<<MPI_Wtime()-t0<<"): total time = "<<(t2-t1)<<" seconds.\n";
} // if - процесс 0
//===================================================================
if (proc_rank==1) // Если это процесс с номером 1
{
int size; // Размер принимаемого массива
int Summa; // Сумма его элементов
t3 = MPI_Wtime(); // Запоминаем момент времени до начала работы
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): started.\n";
// Готовимся принять размер массива
// Ждем сообщение от процесса 0
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): waiting size...\n";
MPI_Recv (&size, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &Status);
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): size=" << size << " received.\n";
size=size/2; // Мы получили размер всего массива,
// но принимать будем только половину
// Динамически выделяем память под принимаемый массив
int *ptr = new int [size];
// Готовимся принять массив данных
// Ждем сообщение от процесса 0
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): waiting array...\n";
MPI_Recv (ptr, size, MPI_INT, 0, 0, MPI_COMM_WORLD, &Status);
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): array received.\n";
// Ищем сумму элементов массива
Summa=0;
for (int i=0; i<size; i++)
Summa+=ptr[i];
cout << " Process 1 ("<<MPI_Wtime()-t0<<"): local summa calculated:"<<Summa<<endl;
// Отсылаем ответ процессу 0
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): sanding Summa...\n";
MPI_Send (&Summa, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): summa sended.\n";
delete [] ptr; // Освобождаем память
cout << " Process 1 ("<< MPI_Wtime()-t0 << "): finished.\n";
t4 = MPI_Wtime(); // Запоминаем момент времени по окончании работы
cout<<" Process 1 ("<< MPI_Wtime()-t0 << "): total time = "<<(t4-t3)<<" sec.\n";
} // if - процесс 1
//===================================================================
MPI_Finalize();
// закрываем MPI-библиотеку
return 0;
}
В начале программы объявляются переменные t0-t4 типа double. Переменная t0 служит для момента времени запуска процесса, переменные t1 и t2 – для хранения начального и конечного времени работы процесса 0, t3 и t4 – процесса 1. В каждом из операторов cout происходит печать значения MPI_Wtime()-t0, которое равно количеству секунд, прошедших от момента t0.
Ниже приведен пример результатов работы программы. Для удобства дальнейшего пояснения каждая строка была вручную пронумерована.
1 Input n: 100000000
2 Process 0 (4.36572): started.
3 Process 0 (6.53677): sanding size...
4 Process 0 (6.53681): size=100000000 sended.
5 Process 0 (6.53682): sanding array...
6 Process 0 (6.65851): array sended.
7 Process 0 (6.77024): local summa calculated, S=250004717.
8 Process 0 (6.77027): waiting answer from process 1...
9 Process 0 (6.7703): answer received, S1=249962239.
10 Process 0 (6.77031): S = 499966956
11 Process 1 (3.01869e-007): started.
12 Process 1 (8.78439e-005): waiting size...
13 Process 1 (6.53681): size=100000000 received.
14 Process 1 (6.60401): waiting array...
15 Process 1 (6.65853): array received.
16 Process 1 (6.77026): local summa calculated:249962239
17 Process 0 (6.86812): finished.
18 Process 0 (6.86815): total time = 2.50244 seconds.
19 Process 1 (6.77029): sanding Summa...
20 Process 1 (6.7703): summa sended.
21 Process 1 (6.82206): finished.
22 Process 1 (6.82216): total time = 6.82216 sec.
Дадим пояснение некоторых моментов в работе программы.
1. В строке 2 указано время старта процесса 0 (4.36 сек.), в строке 11 – время старта процесса 1 (почти ноль). Дело в том, что время старта процесса 0 отсчитывается в программе с момента после ввода значения n с клавиатуры. Очевидно, что в данном примере, ввод с клавиатуры занял 4.36 секунды. Время старта процесса 1 отсчитывается сразу с момента запуска процесса 1, то есть почти сразу после момента времени t0. Но процесс1, запустившись, пока ничего не делает и ждет получения сообщения от процесса 0 (строка 12). Таким образом, 4.36 секунды – это время, потраченное на ввод с клавиатуры, и его не следует относить ко времени обработки массива данных.
2. Между моментами, отмеченными в строках 3 и 4, происходит отправка процессом 0 процессу 1 значения n. Как можно видеть, отправка одного значения типа int занимает десятитысячную долю секунды. Следует, однако, понимать, что в строке 4 указан момент времени, когда сообщение было отправлено процессом 0. Это не значит, что в этот же момент оно было получено процессом 1. Это значит, что в данный момент система MPI приняла сообщение от процесса 0. В реальной жизни этот момент соответствует ситуации, когда с телефона было отправлено SMS-сообщение, или когда бумажное письмо было опущено в почтовый ящик. Сколько времени будет осуществляться доставка сообщения – неизвестно. В худшем случае оно вообще не будет доставлено. Тем не менее, процессу 0 было сообщено, что сообщение ушло, о чем он проинформировал нас в строке 4.
К слову говоря, строка 13 соответствует моменту времени, когда значение n было получено процессом 1. Очевидно, что этот момент совпадает с моментом отправки сообщения процессом 0 (строка 4). Поскольку пример программы запускался на двух ядрах одного процессора, передача сообщения произошла почти мгновенно. Однако в общем случае передача сообщения может занимать достаточно большое время.
3. Строки 5 и 6 соответствуют началу и концу отправки процессом 0 сообщения, содержащего половину массива (50 млн. чисел типа int). Момент вывода строки 15 соответствует моменту приема данного сообщения процессом 1. Разница между временем в строке 15 и временем в строке 5 говорит о том, что процесс передачи и приема этого сообщения занял 0.12 секунды.
4. Все строки, выдаваемые программой на экран, следуют не в хронологическом порядке. Студентам предлагается самостоятельно расставить их в порядке возникновения соответствующих событий. Подсказка: первой будет идти строка 11, затем 12, затем 2, затем 3 и т.д.
Также необходимо обратить внимание на следующее. Как известно, переменные t0-t4, объявляемые в функции main(), создаются внутри каждого процесса. Процесс 0 имеет свои переменные t0-t4, процесс 1 – свои. После создания переменных каждый процесс выполняет команду
t0=MPI_Wtime();
занося в переменную t0 текущий момент времени.
Программист, использующий MPI, должен понимать, что два процесса не могут быть запущены в абсолютно один и тот же момент времени. Моменты запуска каждого из процессов ВСЕГДА различаются. Если процессы запускаются на одном компьютере, то их запуском руководит операционная система, которая сначала запустит один процесс, а потом (пусть даже сразу, без лишних задержек) – второй. Но это будут разные моменты времени. Если в программу после команды t0=MPI_Wtime() добавить строки
Дата добавления: 2016-02-02; просмотров: 1059;