2012-05-08 1 views
3

У меня есть последовательная программа на C++, которую я хочу распараллелить. Я знаю основы MPI, MPI_Send, MPI_Recv и т. Д. В принципе, у меня есть алгоритм генерации данных, который работает значительно быстрее, чем алгоритм обработки данных. В настоящее время они запускаются последовательно, но я думал, что запуск генерации данных в корневом процессе, обработка данных выполняется на подчиненных процессах и отправка сообщения от корня к ведомому, содержащему данные, подлежащие обработке. Таким образом, каждое подчиненное устройство обрабатывает набор данных, а затем ожидает его следующего набора данных.MPI Ведомые процессы зависают, когда больше нет работы

Проблема заключается в том, что после того, как корень процесса выполняется с генерированием данных, программа зависает, потому что ведомые ждут больше.

Это пример задачи:

#include "mpi.h" 

#include <cassert> 
#include <cstdio> 

class Generator { 
    public: 
    Generator(int min, int max) : value(min - 1), max(max) {} 
    bool NextValue() { 
     ++value; 
     return value < max; 
    } 
    int Value() { return value; } 
    private: 
    int value, max; 

    Generator() {} 
    Generator(const Generator &other) {} 
    Generator &operator=(const Generator &other) { return *this; } 
}; 

long fibonnaci(int n) { 
    assert(n > 0); 
    if (n == 1 || n == 2) return 1; 
    return fibonnaci(n-1) + fibonnaci(n-2); 
} 

int main(int argc, char **argv) { 
    MPI_Init(&argc, &argv); 

    int rank, num_procs; 
    MPI_Comm_rank(MPI_COMM_WORLD, &rank); 
    MPI_Comm_size(MPI_COMM_WORLD, &num_procs); 

    if (rank == 0) { 
    Generator generator(1, 2 * num_procs); 
    int proc = 1; 
    while (generator.NextValue()) { 
     int value = generator.Value(); 
     MPI_Send(&value, 1, MPI_INT, proc, 73, MPI_COMM_WORLD); 
     printf("** Sent %d to process %d.\n", value, proc); 
     proc = proc % (num_procs - 1) + 1; 
    } 
    } else { 
    while (true) { 
     int value; 
     MPI_Status status; 
     MPI_Recv(&value, 1, MPI_INT, 0, 73, MPI_COMM_WORLD, &status); 
     printf("** Received %d from process %d.\n", value, status.MPI_SOURCE); 
     printf("Process %d computed %d.\n", rank, fibonnaci(2 * (value + 10))); 
    } 
    } 

    MPI_Finalize(); 
    return 0; 
} 

Очевидно, что не все, что выше «хорошая практика», но достаточно, чтобы получить точку в поперечнике.

Если я удалю while(true) из подчиненных процессов, программа выйдет, когда выйдут все подчиненные устройства. Я хотел бы, чтобы программа выходила только после того, как корневой процесс выполнил свою работу, и все подчиненные обработали все, что было отправлено.

Если бы я знал, сколько наборов данных будет сгенерировано, я мог бы запустить этот процесс, и все будет нормально работать, но это не так.

Любые предложения? Есть ли что-нибудь в API, который это сделает? Может ли это быть лучше с лучшей топологией? Будет ли MPI_Isend или MPI_IRecv сделать это лучше? Я довольно новичок в MPI, так что несите меня.

Благодаря

+0

Реализация фибоначчи - это O (2^n).Вы должны оптимизировать свой последовательный алгоритм. – mfontanini

+0

Я знаю. это не настоящая проблема, которую я решаю, это просто самый простой пример, который я мог бы подумать о том, что смоделировала проблему. –

+0

Возможно, мне что-то не хватает, но не будет ли простой барьер в конце каждого процесса решить вашу проблему? – suszterpatt

ответ

5

Обычная практика для отправки всех рабочих процессов пустое сообщение с помощью специального тега, который сигнализирует о них, чтобы выйти из бесконечного цикла обработки. Скажем, этот тег 42. Вы бы сделать что-то подобное, что в цикле рабочего:

while (true) { 
    int value; 
    MPI_Status status; 
    MPI_Recv(&value, 1, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status); 
    if (status.MPI_TAG == 42) { 
    printf("Process %d exiting work loop.\n", rank); 
    break; 
    } 
    printf("** Received %d from process %d.\n", value, status.MPI_SOURCE); 
    printf("Process %d computed %d.\n", rank, fibonnaci(2 * (value + 10))); 
} 

Процесс менеджер должен сделать что-то вроде этого после цикла генератора:

for (int i = 1; i < num_procs; i++) 
    MPI_Send(&i, 0, MPI_INT, i, 42, MPI_COMM_WORLD); 

Что касается вашего следующего вопроса. Использование MPI_Isend() в главном процессе приведет к десериализации исполнения и увеличению производительности. Правда, правда, вы отправляете очень маленькие сообщения, и обычно они буферизуются внутри (ПРЕДУПРЕЖДЕНИЕ - зависит от реализации!), поэтому ваш MPI_Send() фактически не блокирует, и вы уже имеете несекретное выполнение. MPI_Isend() возвращает дескриптор MPI_Request, который вам нужно позаботиться позже. Вы можете либо дождаться, пока он закончится с MPI_Wait(), либо MPI_Waitall(), но вы также можете просто позвонить MPI_Request_free(), и он будет автоматически освобожден, когда операция закончена. Обычно это делается, когда вы хотите отправить много сообщений асинхронно и не заботятся о том, когда отправления будут завершены, но это плохая практика, тем не менее, поскольку большое количество выдающихся запросов может потреблять много драгоценной памяти. Что касается рабочих процессов - им нужны данные для продолжения вычисления, поэтому использование MPI_Irecv() не требуется.

Добро пожаловать в прекрасный мир программирования MPI!

+0

Это именно то, что я ищу. Я даже не думал об использовании тегов таким образом. Довольно круто. Что касается маленьких сообщений, это просто упрощенный пример, фактические сообщения, которые я буду отправлять, намного больше, поэтому мне нужно будет играть с MPI_Send и MPI_Isend, чтобы узнать, какая из них дает лучшую производительность. Спасибо, я ценю помощь. –

+1

Просто примечание стороны - 'MPI_Isend' не отправляет сообщения быстрее, чем' MPI_Send', но позволяет накладывать связь и вычисления и, таким образом, скрывать латентность первого. –

+0

Спасибо, что указали это. Я знаю об этом, но у меня недостаточно опыта работы с MPI, чтобы узнать, что еще лучше. Время для некоторых экспериментов. Спасибо за совет. –