Я пишу программу оптимизации с использованием MPI-2, в которой мне нужно иметь std::vector
равной длины std::vector
s (концептуально), общий для всех процессов. Этот вектор содержит лучшее k
решения проблемы, найденной в настоящее время, и обновляется каждый раз, когда одно из многих процессов MPI находит новое лучшее решение. Время, затрачиваемое каждым процессом на поиск нового решения, обычно сильно варьируется.
Мой вопрос, учитывая проблемы с производительностью в синхронизации и ожидания, должен ли я использовать коллективы MPI, такие как MPI_allgather
каждый раз, когда новое лучшее решение найдено; или я должен использовать одностороннюю связь в MPI-2 для поддержания «общего» вектора среди всех процессов.
В частности, если я использую MPI_allgather
Будут ли процессы, завершающие свою работу в начале, простаивать и ждать какой-то синхронизации с другими процессами?
У меня есть некоторый опыт работы с MPI точка-точка связи (обн: также как UPC), но я не использовал коллективные или односторонние коммуникации в реальном кодировании. Я искал SO и нашел соответствующие вопросы / ответы о MPI_allgathers, например, Распределить структуру, используя MPI_Allgather , а про одностороннее общение Создание счетчика, который остается синхронизированным между процессами MPI. Но я испытываю затруднения, говоря точную разницу между этими двумя подходами.
Спасибо,
— Обновить —
В частности, у меня есть пример кода внизу от Создание счетчика, который остается синхронизированным между процессами MPI, который использует односторонний, чтобы поддерживать один int
«общий». Я пытался приспособить его для работы с универсальным типом, но не знаю, как заставить его работать, так как у меня проблемы с пониманием исходного кода и почему он поддерживает массив data
и как я мог обобщить MPI_Accumulate
пользовательской функции (например, простая замена старого вектора на новый).
template // note: T могут быть только примитивными типами (не указатель, ref или struct), такими как int и double.
struct mpi_array {
typedef std :: vector Vector;
MPI_Win win;
int hostrank;
int rank;
размер int;
Вектор вал;
Vector * hostvals;
};
Односторонний код счетчика:
#include <mpi.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
struct mpi_counter_t {
MPI_Win win;
int hostrank ;
int myval;
int *data;
int rank, size;
};
struct mpi_counter_t *create_counter(int hostrank) {
struct mpi_counter_t *count;
count = (struct mpi_counter_t *)malloc(sizeof(struct mpi_counter_t));
count->hostrank = hostrank;
MPI_Comm_rank(MPI_COMM_WORLD, &(count->rank));
MPI_Comm_size(MPI_COMM_WORLD, &(count->size));
if (count->rank == hostrank) {
MPI_Alloc_mem(count->size * sizeof(int), MPI_INFO_NULL, &(count->data));
for (int i=0; i<count->size; i++) count->data[i] = 0;
MPI_Win_create(count->data, count->size * sizeof(int), sizeof(int),
MPI_INFO_NULL, MPI_COMM_WORLD, &(count->win));
} else {
count->data = NULL;
MPI_Win_create(count->data, 0, 1,
MPI_INFO_NULL, MPI_COMM_WORLD, &(count->win));
}
count -> myval = 0;
return count;
}
int increment_counter(struct mpi_counter_t *count, int increment) {
int *vals = (int *)malloc( count->size * sizeof(int) );
int val;
MPI_Win_lock(MPI_LOCK_EXCLUSIVE, count->hostrank, 0, count->win);
for (int i=0; i<count->size; i++) {
if (i == count->rank) {
MPI_Accumulate(&increment, 1, MPI_INT, 0, i, 1, MPI_INT, MPI_SUM,
count->win);
} else {
MPI_Get(&vals[i], 1, MPI_INT, 0, i, 1, MPI_INT, count->win);
}
}
MPI_Win_unlock(0, count->win);
count->myval += increment;
vals[count->rank] = count->myval;
val = 0;
for (int i=0; i<count->size; i++)
val += vals[i];
free(vals);
return val;
}
void delete_counter(struct mpi_counter_t **count) {
if ((*count)->rank == (*count)->hostrank) {
MPI_Free_mem((*count)->data);
}
MPI_Win_free(&((*count)->win));
free((*count));
*count = NULL;
return;
}
void print_counter(struct mpi_counter_t *count) {
if (count->rank == count->hostrank) {
for (int i=0; i<count->size; i++) {
printf("%2d ", count->data[i]);
}
puts("");
}
}
int test1() {
struct mpi_counter_t *c;
int rank;
int result;
c = create_counter(0);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
result = increment_counter(c, 1);
printf("%d got counter %d\n", rank, result);
MPI_Barrier(MPI_COMM_WORLD);
print_counter(c);
delete_counter(&c);
}int test2() {
const int WORKITEMS=50;
struct mpi_counter_t *c;
int rank;
int result = 0;
c = create_counter(0);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
srandom(rank);
while (result < WORKITEMS) {
result = increment_counter(c, 1);
if (result <= WORKITEMS) {
printf("%d working on item %d...\n", rank, result);
sleep(random() % 10);
} else {
printf("%d done\n", rank);
}
}
MPI_Barrier(MPI_COMM_WORLD);
print_counter(c);
delete_counter(&c);
}
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
test1();
test2();
MPI_Finalize();
}
Вы обеспокоены тем, что некоторые процессы могут войти в MPI_ALLGATHER
прежде чем другие действительны, но это всегда имеет место в любом приложении с синхронизацией, а не только в тех, которые явно используют коллективное общение.
Однако может показаться, что вы неправильно понимаете, что делают односторонние операции. Они не предоставляют модель параллельного глобального адресного пространства (PGAS), где все синхронизировано для вас. Вместо этого они просто дают вам возможность напрямую обращаться к памяти удаленных процессов. Память каждого процесса все еще отделена. Кроме того, если вы собираетесь обновляться от точки к точке до остальной части MPI, я бы не стал ограничиваться только функциями MPI-2. В MPI-3 есть некоторые новые вещи, которые также улучшают как коллективные, так и односторонние (особенно последние).
Несмотря на все сказанное, если вы никогда не использовали ничего, кроме двухточечного, одностороннее движение станет для вас большим прыжком. Возможно, вы захотите пойти на более промежуточный шаг и сначала проверить коллективы. Если вы все еще не довольны своей эффективностью, вы можете взглянуть на одностороннюю главу, но она очень сложна, и большинство людей обычно заканчивают тем, что используют что-то, что находится поверх односторонней, а не используют ее напрямую (например, некоторые из языков PGAS возможно).