2017-01-05 4 views
3

Я не хочу использовать mpiexec -n 4 ./a.out для запуска моей программы на моем основном процессоре i7 (с 4 ядрами). Вместо этого я хочу запустить ./a.out, определить количество ядер и запустить MPI для запуска процесса на ядро.Как программно определить количество ядер и запустить программу MPI с использованием всех ядер

Этот вопрос и ответ MPI Number of processors? привели меня к использованию mpiexec.

Причина, по которой я хочу избежать mpiexec, заключается в том, что мой код предназначен для библиотеки в рамках более крупного проекта, над которым я работаю. В более крупном проекте есть GUI, и пользователь начнет длительные вычисления, которые вызовут мою библиотеку, которая, в свою очередь, будет использовать MPI. Интеграция между пользовательским интерфейсом и вычислительным кодом не является тривиальной ... поэтому запуск внешнего процесса и обмен данными через сокет или некоторые другие средства не являются опцией. Это должен быть вызов библиотеки.

Возможно ли это? Как мне это сделать?

+0

Конечно, использование 7 из 24 ядер в порядке. Возникает вопрос: как я могу запустить эти 7 процессов программным способом вместо использования mpiexec? – Jason

ответ

4

Это совершенно нетривиальная вещь для достижения в целом. Кроме того, практически нет портативного решения, которое не зависит от некоторых особенностей реализации MPI. Ниже следует примерное решение, которое работает с Open MPI и, возможно, с другими общими реализациями MPI (MPICH, Intel MPI и т. Д.). Он включает в себя второй исполняемый файл или средство для первоначального исполняемого файла, чтобы напрямую вызвать вашу библиотеку, предоставив некоторый специальный аргумент командной строки. Все идет так.

Предположите, что оригинал был запущен просто как ./a.out. Когда вы вызываете функцию библиотеки, она вызывает MPI_Init(NULL, NULL), которая инициализирует MPI. Поскольку исполняемый файл не запускался через mpiexec, он возвращается к так называемой инициализации MPI одноименного , т. Е. Создает задание MPI, состоящее из одного процесса. Чтобы выполнить распределенные вычисления, вам нужно запустить больше процессов MPI, и в этом случае ситуация осложняется в общем случае.

MPI поддерживает динамическое управление процессами, в котором одно задание MPI может запускать второе и обмениваться данными с помощью intercommunicators. Это происходит, когда первое задание вызывает MPI_Comm_spawn или MPI_Comm_spawn_multiple. Первый используется для запуска простых заданий MPI, которые используют один и тот же исполняемый файл для всех рангов MPI, тогда как второй может запускать задания, которые смешивают разные исполняемые файлы. Оба требуют информации о том, где и как запускать процессы. Это происходит из так называемой вселенной MPI, которая предоставляет информацию не только о запущенных процессах, но также о доступных слотах для динамически запущенных. Вселенная построена на mpiexec или каким-либо другим механизмом запуска, который принимает, например, файл хоста со списком узлов и количеством слотов на каждом узле. В отсутствие такой информации некоторые реализации MPI (включая Open MPI) будут просто запускать исполняемые файлы на том же узле, что и исходный файл. MPI_Comm_spawn[_multiple] имеет аргумент MPI_Info, который может использоваться для предоставления списка паритета с ключом с информацией о реализации. Открытый MPI поддерживает ключ add-hostfile, который может использоваться для указания файла хоста, который будет использоваться при нерестах дочернего задания. Это полезно, например, для того, чтобы пользователь мог указать через GUI список хостов, которые будут использоваться для вычисления MPI. Но давайте сосредоточимся на случае, когда такая информация не предоставляется, и Open MPI просто запускает дочернее задание на том же хосте.

Предположим, что рабочий исполняемый файл называется worker.Или что исходный исполняемый файл может служить рабочим, если он вызван с помощью специального параметра командной строки, например -worker. Если вы хотите выполнить вычисления с N процессами, вам необходимо запустить N-1 работников. Это просто:

(отдельный исполняемый)

MPI_Comm child_comm; 
MPI_Comm_spawn("./worker", MPI_ARGV_NULL, N-1, MPI_INFO_NULL, 0, 
       MPI_COMM_SELF, &child_comm, MPI_ERRCODES_IGNORE); 

(тот же исполняемый файл, с возможностью)

MPI_Comm child_comm; 
char *argv[] = { "-worker", NULL }; 
MPI_Comm_spawn("./a.out", argv, N-1, MPI_INFO_NULL, 0, 
       MPI_COMM_SELF, &child_comm, MPI_ERRCODES_IGNORE); 

Если все идет хорошо, child_comm будет установлен на ручке с интеркоммуникатора, который может использоваться для связи с новым заданием. Поскольку интеркоммуникаторы являются довольно сложными в использовании, а отдел работы с родительским ребенком требует сложной программной логики, можно просто объединить две стороны межкоммуникатора в коммуникатор «большой мир», который заменил MPI_COMM_WORLD. Со стороны родителей:

MPI_Comm bigworld; 
MPI_Intercomm_merge(child_comm, 0, &bigworld); 

На стороне ребенка:

MPI_Comm parent_comm, bigworld; 
MPI_Get_parent(&parent_comm); 
MPI_Intercomm_merge(parent_comm, 1, &bigworld); 

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

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

main.c

#include <stdio.h> 
#include <mpi.h> 

int main (void) 
{ 
    MPI_Init(NULL, NULL); 

    printf("[main] Spawning workers...\n"); 

    MPI_Comm child_comm; 
    MPI_Comm_spawn("./worker", MPI_ARGV_NULL, 2, MPI_INFO_NULL, 0, 
        MPI_COMM_SELF, &child_comm, MPI_ERRCODES_IGNORE); 

    MPI_Comm bigworld; 
    MPI_Intercomm_merge(child_comm, 0, &bigworld); 

    int size, rank; 
    MPI_Comm_rank(bigworld, &rank); 
    MPI_Comm_size(bigworld, &size); 
    printf("[main] Big world created with %d ranks\n", size); 

    // Perform some computation 
    int data = 1, result; 
    MPI_Bcast(&data, 1, MPI_INT, 0, bigworld); 
    data *= (1 + rank); 
    MPI_Reduce(&data, &result, 1, MPI_INT, MPI_SUM, 0, bigworld); 
    printf("[main] Result = %d\n", result); 

    MPI_Barrier(bigworld); 

    MPI_Comm_free(&bigworld); 
    MPI_Comm_free(&child_comm); 

    MPI_Finalize(); 
    printf("[main] Shutting down\n"); 
    return 0; 
} 

worker.c

#include <stdio.h> 
#include <mpi.h> 

int main (void) 
{ 
    MPI_Init(NULL, NULL); 

    MPI_Comm parent_comm; 
    MPI_Comm_get_parent(&parent_comm); 

    int rank, size; 
    MPI_Comm_rank(MPI_COMM_WORLD, &rank); 
    MPI_Comm_size(MPI_COMM_WORLD, &size); 
    printf("[worker] %d of %d here\n", rank, size); 

    MPI_Comm bigworld; 
    MPI_Intercomm_merge(parent_comm, 1, &bigworld); 

    MPI_Comm_rank(bigworld, &rank); 
    MPI_Comm_size(bigworld, &size); 
    printf("[worker] %d of %d in big world\n", rank, size); 

    // Perform some computation 
    int data; 
    MPI_Bcast(&data, 1, MPI_INT, 0, bigworld); 
    data *= (1 + rank); 
    MPI_Reduce(&data, NULL, 1, MPI_INT, MPI_SUM, 0, bigworld); 

    printf("[worker] Done\n"); 
    MPI_Barrier(bigworld); 

    MPI_Comm_free(&bigworld); 
    MPI_Comm_free(&parent_comm); 

    MPI_Finalize(); 
    return 0; 
} 

Вот как это работает:

$ mpicc -o main main.c 
$ mpicc -o worker worker.c 
$ ./main 
[main] Spawning workers... 
[worker] 0 of 2 here 
[worker] 1 of 2 here 
[worker] 1 of 3 in big world 
[worker] 2 of 3 in big world 
[main] Big world created with 3 ranks 
[worker] Done 
[worker] Done 
[main] Result = 6 
[main] Shutting down 

Ребенок работа должна использовать MPI_Comm_get_parent для получения интеркоммуникатор на родительскую работу. Когда процесс не является частью такого дочернего задания, возвращаемое значение будет MPI_COMM_NULL. Это позволяет легко реализовать как основную программу, так и работника в одном и том же исполняемом файле. Вот гибридная пример:

#include <stdio.h> 
#include <mpi.h> 

MPI_Comm bigworld_comm = MPI_COMM_NULL; 
MPI_Comm other_comm = MPI_COMM_NULL; 

int parlib_init (const char *argv0, int n) 
{ 
    MPI_Init(NULL, NULL); 

    MPI_Comm_get_parent(&other_comm); 
    if (other_comm == MPI_COMM_NULL) 
    { 
     printf("[main] Spawning workers...\n"); 
     MPI_Comm_spawn(argv0, MPI_ARGV_NULL, n-1, MPI_INFO_NULL, 0, 
         MPI_COMM_SELF, &other_comm, MPI_ERRCODES_IGNORE); 
     MPI_Intercomm_merge(other_comm, 0, &bigworld_comm); 
     return 0; 
    } 

    int rank, size; 
    MPI_Comm_rank(MPI_COMM_WORLD, &rank); 
    MPI_Comm_size(MPI_COMM_WORLD, &size); 
    printf("[worker] %d of %d here\n", rank, size); 
    MPI_Intercomm_merge(other_comm, 1, &bigworld_comm); 
    return 1; 
} 

int parlib_dowork (void) 
{ 
    int data = 1, result = -1, size, rank; 

    MPI_Comm_rank(bigworld_comm, &rank); 
    MPI_Comm_size(bigworld_comm, &size); 

    if (rank == 0) 
    { 
     printf("[main] Doing work with %d processes in total\n", size); 
     data = 1; 
    } 

    MPI_Bcast(&data, 1, MPI_INT, 0, bigworld_comm); 
    data *= (1 + rank); 
    MPI_Reduce(&data, &result, 1, MPI_INT, MPI_SUM, 0, bigworld_comm); 

    return result; 
} 

void parlib_finalize (void) 
{ 
    MPI_Comm_free(&bigworld_comm); 
    MPI_Comm_free(&other_comm); 
    MPI_Finalize(); 
} 

int main (int argc, char **argv) 
{ 
    if (parlib_init(argv[0], 4)) 
    { 
     // Worker process 
     (void)parlib_dowork(); 
     printf("[worker] Done\n"); 
     parlib_finalize(); 
     return 0; 
    } 

    // Main process 
    // Show GUI, save the world, etc. 
    int result = parlib_dowork(); 
    printf("[main] Result = %d\n", result); 
    parlib_finalize(); 

    printf("[main] Shutting down\n"); 
    return 0; 
} 

А вот пример вывода:

$ mpicc -o hybrid hybrid.c 
$ ./hybrid 
[main] Spawning workers... 
[worker] 0 of 3 here 
[worker] 2 of 3 here 
[worker] 1 of 3 here 
[main] Doing work with 4 processes in total 
[worker] Done 
[worker] Done 
[main] Result = 10 
[worker] Done 
[main] Shutting down 

Некоторые вещи, чтобы иметь в виду при проектировании таких параллельных библиотек:

  • MPI может быть инициализированы только один раз. При необходимости вызовите MPI_Initialized, чтобы проверить, была ли библиотека уже инициализирована.
  • MPI может быть завершен только один раз. Опять же, MPI_Finalized - твой друг. Его можно использовать как-то вроде обработчика atexit() для реализации универсальной финализации MPI при выходе из программы.
  • При использовании в поточных контекстах (обычно при использовании графических интерфейсов), MPI должен быть инициализирован с поддержкой потоков. См. MPI_Init_thread.
+0

Спасибо за этот тщательный ответ! По-моему, мой подход и использование MPI - не правильное решение для моей проблемы. К сожалению, это не моя библиотека, которая использует MPI. Моя библиотека зависит от библиотеки, которая использует MPI. Кажется, лучшим решением для меня является изменение библиотеки сторонних разработчиков, чтобы использовать что-то вроде OpenMP (no «I»). – Jason

+0

Если вы не собираетесь масштабироваться за пределы одного компьютера, то MPI действительно является излишним. Хотя, если вы основываете решение на MPI и делаете это правильно, позже он может легко масштабироваться за один вычислительный узел. Но OpenMP действительно проще, имеет меньше зависимостей и не требует специального запуска/среды для работы. –

0

Вы можете получить количество процессоров, используя, например, this решение, а затем запустите процесс MPI, вызвав MPI_comm_spawn. Но вам нужно будет иметь отдельный исполняемый файл.