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; просмотров: 1070;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.058 сек.