Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Tutorial

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 24. Boost.MPI

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Tutorial

Повышаю. Программа MPI состоит из множества взаимодействующих процессов (возможно, работающих на разных компьютерах), которые общаются между собой путем передачи сообщений. Повышаю. MPI - это библиотека (как и MPI нижнего уровня), а не язык, поэтому первый шаг в увеличении. MPI - это создание объекта<mpi::environment>, который инициализирует среду MPI и обеспечивает связь между процессами. Объект<mpi::environment>инициализируется с помощью аргументов программы (которые он может изменить) в вашей основной программе. Создание этого объекта инициализирует MPI, а его разрушение завершит MPI. Подавляющее большинство Boost. MPI программы, экземпляр<mpi::environment>будет объявлен в<main>в самом начале программы.

Связь с MPI всегда происходит поверхкоммуникатора, который может быть создан просто по умолчанию-конструируя объект типа<mpi::communicator>. Затем этот коммуникатор может быть запрошен, чтобы определить, сколько процессов выполняется («размер» коммуникатора) и дать уникальное число каждому процессу, от нуля до размера коммуникатора (т.е. «ранг» процесса):

#include <boost/mpi/environment.hpp>
#include <boost/mpi/communicator.hpp>
#include <iostream>
namespace mpi = boost::mpi;
int main()
{
  mpi::environment env;
  mpi::communicator world;
  std::cout << "I am process " << world.rank() << " of " << world.size()
            << "." << std::endl;
  return 0;
}

Например, если вы запустите эту программу с 7 процессами, вы получите выход, такой как:

I am process 5 of 7.
I am process 0 of 7.
I am process 1 of 7.
I am process 6 of 7.
I am process 2 of 7.
I am process 4 of 7.
I am process 3 of 7.

Конечно, процессы могут выполняться в разном порядке каждый раз, поэтому ряды не могут быть строго увеличены. Более интересно то, что текст может полностью испортиться, потому что один процесс может начать писать «Я — процесс» до того, как другой процесс закончит писать «7».

Если у вас все еще есть библиотека MPI, поддерживающая только MPI 1.1, вам нужно будет передать аргументы командной строки конструктору окружения, как показано в этом примере:

#include <boost/mpi/environment.hpp>
#include <boost/mpi/communicator.hpp>
#include <iostream>
namespace mpi = boost::mpi;
int main(int argc, char* argv[])
{
  mpi::environment env(argc, argv);
  mpi::communicator world;
  std::cout << "I am process " << world.rank() << " of " << world.size()
            << "." << std::endl;
  return 0;
}

Point-to-Point communication

Как библиотека передачи сообщений, основная цель MPI заключается в рутинных сообщениях от одного процесса к другому, то есть от точки к точке. MPI содержит процедуры, которые могут отправлять сообщения, получать сообщения и запрашивать, доступны ли сообщения. Каждое сообщение имеет исходный процесс, целевой процесс, тег и полезную нагрузку, содержащую произвольные данные. Исходным и целевым процессами являются ранги отправителя и получателя сообщения соответственно. Теги - это целые числа, которые позволяют получателю различать различные сообщения, поступающие от одного и того же отправителя.

Следующая программа использует два процесса MPI, чтобы написать «Привет, мир!» на экране<hello_world.cpp>:

#include <boost/mpi.hpp>
#include <iostream>
#include <string>
#include <boost/serialization/string.hpp>
namespace mpi = boost::mpi;
int main()
{
  mpi::environment env;
  mpi::communicator world;
  if (world.rank() == 0) {
    world.send(1, 0, std::string("Hello"));
    std::string msg;
    world.recv(1, 1, msg);
    std::cout << msg << "!" << std::endl;
  } else {
    std::string msg;
    world.recv(0, 0, msg);
    std::cout << msg << ", ";
    std::cout.flush();
    world.send(0, 1, std::string("world"));
  }
  return 0;
}

Первый процессор (ранг 0) передает сообщение «Привет» второму процессору (ранг 1) с помощью тега 0. Второй процессор печатает полученную строку вместе с запятой, а затем передает сообщение «мир» обратно в процессор 0 с другим тегом. Затем первый процессор записывает это сообщение с помощью «!» и выходит. Все посылки выполняются методом<communicator::send>, и все приемники используют соответствующий<communicator::recv>вызов.

Non-blocking communication

Операции связи MPI по умолчанию -<send>и<recv>- могут подождать, пока вся передача не будет завершена, прежде чем они смогут вернуться. Иногда этоблокирующееповедение оказывает негативное влияние на производительность, потому что отправитель может выполнять полезные вычисления, пока он ждет передачи. Более важными, однако, являются случаи, когда несколько операций связи должны происходить одновременно, например, процесс будет одновременно отправлять и получать.

Давайте вернемся к нашей программе «Привет, мир!» из предыдущего раздела. Ядро этой программы передает два сообщения:

if (world.rank() == 0) {
  world.send(1, 0, std::string("Hello"));
  std::string msg;
  world.recv(1, 1, msg);
  std::cout << msg << "!" << std::endl;
} else {
  std::string msg;
  world.recv(0, 0, msg);
  std::cout << msg << ", ";
  std::cout.flush();
  world.send(0, 1, std::string("world"));
}

Первый процесс передает сообщение второму процессу, затем готовится к получению сообщения. Второй процесс выполняет отправку и получение в обратном порядке. Тем не менее, эта последовательность событий — это просто последовательность, что означает, что по существу нет параллелизма. Мы можем использовать неблокирующую связь для обеспечения одновременной передачи двух сообщений<hello_world_nonblocking.cpp>:

#include <boost/mpi.hpp>
#include <iostream>
#include <string>
#include <boost/serialization/string.hpp>
namespace mpi = boost::mpi;
int main()
{
  mpi::environment env;
  mpi::communicator world;
  if (world.rank() == 0) {
    mpi::request reqs[2];
    std::string msg, out_msg = "Hello";
    reqs[0] = world.isend(1, 0, out_msg);
    reqs[1] = world.irecv(1, 1, msg);
    mpi::wait_all(reqs, reqs + 2);
    std::cout << msg << "!" << std::endl;
  } else {
    mpi::request reqs[2];
    std::string msg, out_msg = "world";
    reqs[0] = world.isend(0, 1, out_msg);
    reqs[1] = world.irecv(0, 0, msg);
    mpi::wait_all(reqs, reqs + 2);
    std::cout << msg << ", ";
  }
  return 0;
}

Мы заменили призывы к<communicator::send>и<communicator::recv>членам аналогичными призывами к их неблокирующим коллегам<communicator::isend>и<communicator::irecv>. Префиксiуказывает, что операции немедленно возвращаются с объектом<mpi::request>, что позволяет запрашивать статус запроса связи (см. метод<test>) или ждать, пока он не будет завершен (см. метод<wait>). Несколько запросов могут быть выполнены одновременно с операцией<wait_all>.

Важное примечание: Стандарт MPI требует, чтобы пользователи сохраняли обработку запроса для неблокирующей связи и вызывали операцию «подождать» (или успешно тестировать для завершения) для завершения отправки или получения. В отличие от большинства реализаций C MPI, которые позволяют пользователю отклонить запрос на неблокирующую отправку, Boost. MPI требует, чтобы пользователь назвал «ожидание» или «тест», поскольку объект запроса может содержать временные буферы, которые должны храниться до завершения отправки. Более того, стандарт MPI не гарантирует, что приемник делает какой-либо прогресс перед вызовом «ждать» или «тестировать», хотя большинство реализаций C MPI позволяют приемникам прогрессировать перед вызовом «ждать» или «тестировать». С другой стороны, Boost.MPI обычно требует «тестирования» или «ожидания» звонков для достижения прогресса.

Если вы запускаете эту программу несколько раз, вы можете увидеть некоторые странные результаты, а именно:

Hello, world!

Другие будут производить:

world!
Hello,

или даже какой-то искаженный вариант букв в «Привет» и «мир». Это указывает на некоторый параллелизм в программе, потому что после того, как оба сообщения (одновременно) передаются, оба процесса будут одновременно выполнять свои заявления печати. Как для производительности, так и для корректности неблокирующие операции связи имеют решающее значение для многих параллельных приложений с использованием MPI.

User-defined data types

Включение<boost/serialization/string.hpp>в предыдущие примеры очень важно: оно делает значения типа<std::string>сериализуемыми, чтобы их можно было передавать с помощью Boost. MPI. В целом, встроенные типы C++ (<int>s,<float>s, символы и т.д.) могут передаваться по MPI напрямую, в то время как пользовательские и библиотечные типы должны сначала быть сериализованы (упакованы) в формат, который поддается передаче. Повышаю. MPI опирается на библиотекуBoost.Serializationдля сериализации и десериализации типов данных.

Для типов, определенных стандартной библиотекой (например,<std::string>или<std::vector>) и некоторых типов в Boost (например,<boost::variant>), библиотекаBoost.Serializationуже содержит весь требуемый код сериализации. В этих случаях вам нужно только включить соответствующий заголовок из каталога<boost/serialization>.

Для типов, которые еще не имеют заголовка сериализации, сначала необходимо реализовать код сериализации, прежде чем типы могут быть переданы с помощью Boost. MPI. Рассмотрим простой класс<gps_position>, который содержит членов<degrees>,<minutes>и<seconds>. Этот класс становится сериализуемым, делая его другом<boost::serialization::access>и вводя шаблонную<serialize()>функцию следующим образом:

class gps_position
{
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position(){};
    gps_position(int d, int m, float s) :
        degrees(d), minutes(m), seconds(s)
    {}
};

Полная информация о том, как сделать типы сериализуемыми, выходит за рамки этого учебника. Для получения дополнительной информации см.Boost.Serializationбиблиотечный учебник, из которого был извлечен приведенный выше пример. Одно из важных побочных преимуществ создания типов, сериализуемых для Boost. MPI заключается в том, что они становятся сериализуемыми для любого другого использования, такого как хранение объектов на диске и манипулирование ими в XML.

Некоторые сериализуемые типы, такие как<gps_position>выше, имеют фиксированное количество данных, хранящихся при фиксированных смещениях, и полностью определяются значениями их элемента данных (большинство POD без указателей являются хорошим примером). Если это так, поднимите. MPI может оптимизировать их сериализацию и передачу, избегая посторонних операций копирования. Чтобы обеспечить эту оптимизацию, пользователи должны специализироваться на признаке типа<is_mpi_datatype>, например:

namespace boost { namespace mpi {
  template <>
  struct is_mpi_datatype<gps_position> : mpl::true_ { };
} }

Для типов без шаблонов мы определили макрос, чтобы упростить объявление типа как типа данных MPI.

BOOST_IS_MPI_DATATYPE(gps_position)

Для композитных признаков специализация<is_mpi_datatype>может зависеть от самого<is_mpi_datatype>. Например, объект<boost::array>фиксируется только тогда, когда фиксируется тип хранимого им параметра:

namespace boost { namespace mpi {
  template <typename T, std::size_t N>
  struct is_mpi_datatype<array<T, N> >
    : public is_mpi_datatype<T> { };
} }

Оптимизация удаления избыточной копии может быть применена только тогда, когда форма типа данных полностью фиксирована. Типы с переменной длиной (например, строки, связанные списки) и типы, которые хранят указатели, не могут использовать оптимизацию, но увеличивают. MPI не сможет обнаружить эту ошибку во время компиляции. Попытка выполнить эту оптимизацию, когда она неверна, скорее всего, приведет к ошибкам сегментации и другому странному поведению программы.

Повышаю. MPI может передавать любой тип данных, определенный пользователем, из одного процесса в другой. Встроенные типы могут передаваться без каких-либо дополнительных усилий; определяемые библиотекой типы требуют включения заголовка сериализации; и определяемые пользователем типы потребуют добавления кода сериализации. Фиксированные типы данных могут быть оптимизированы для передачи с использованием признака типа<is_mpi_datatype>.

Collective operations

Операции «точка-точка»являются основными сообщениями, передающими примитивы в Boost. MPI. Однако многие приложения для передачи сообщений также требуют алгоритмов связи более высокого уровня, которые объединяют или обобщают данные, хранящиеся во многих различных процессах. Эти алгоритмы поддерживают множество общих задач, таких как «трансляция этого значения для всех процессов», «вычисление суммы значений на всех процессорах» или «нахождение глобального минимума».

Broadcast

Алгоритм<broadcast>на сегодняшний день является самой простой коллективной операцией. Он передает значение от одного процесса ко всем другим процессам в пределах 70. Например, следующая программа транслирует «Привет, мир!» от процесса 0 до любого другого процесса.<hello_world_broadcast.cpp>

#include <boost/mpi.hpp>
#include <iostream>
#include <string>
#include <boost/serialization/string.hpp>
namespace mpi = boost::mpi;
int main()
{
  mpi::environment env;
  mpi::communicator world;
  std::string value;
  if (world.rank() == 0) {
    value = "Hello, World!";
  }
  broadcast(world, value, 0);
  std::cout << "Process #" << world.rank() << " says " << value
            << std::endl;
  return 0;
}

Запуск этой программы с семью процессами даст такой результат, как:

Process #0 says Hello, World!
Process #2 says Hello, World!
Process #1 says Hello, World!
Process #4 says Hello, World!
Process #3 says Hello, World!
Process #5 says Hello, World!
Process #6 says Hello, World!

Gather

Коллектив<gather>собирает значения, производимые каждым процессом в коммуникаторе, в вектор значений на «корневом» процессе (определяется аргументом<gather>). Элемент /i/th в векторе будет соответствовать значению, собранному в процессе /i/th. Например, в следующей программе каждый процесс вычисляет свое собственное случайное число. Все эти случайные числа собираются в процессе 0 («корень» в данном случае), который распечатывает значения, соответствующие каждому процессору.<random_gather.cpp>

#include <boost/mpi.hpp>
#include <iostream>
#include <vector>
#include <cstdlib>
namespace mpi = boost::mpi;
int main()
{
  mpi::environment env;
  mpi::communicator world;
  std::srand(time(0) + world.rank());
  int my_number = std::rand();
  if (world.rank() == 0) {
    std::vector<int> all_numbers;
    gather(world, my_number, all_numbers, 0);
    for (int proc = 0; proc < world.size(); ++proc)
      std::cout << "Process #" << proc << " thought of "
                << all_numbers[proc] << std::endl;
  } else {
    gather(world, my_number, 0);
  }
  return 0;
}

Выполнение этой программы с помощью семи процессов приведет к выходу, например: Хотя случайные значения будут меняться от одного прогона к следующему, порядок процессов на выходе останется прежним, потому что только процесс 0 записывает<std::cout>.

Process #0 thought of 332199874
Process #1 thought of 20145617
Process #2 thought of 1862420122
Process #3 thought of 480422940
Process #4 thought of 1253380219
Process #5 thought of 949458815
Process #6 thought of 650073868

Операция<gather>собирает значения из каждого процесса в вектор в одном процессе. Если вместо этого значения каждого процесса должны быть собраны в идентичные векторы на каждом процессе, используйте алгоритм<all_gather>, который семантически эквивалентен вызову<gather>, за которым следует<broadcast>полученного вектора.

Reduce

Коллектив<reduce>суммирует значения из каждого процесса в одно значение в заданном пользователем «корневом» процессе. Начало. Операция MPI<reduce>аналогична по духу операции STL.<accumulate>операция, поскольку она принимает последовательность значений (по одному на процесс) и объединяет их через объект функции. Например, мы можем произвольно генерировать значения в каждом процессе и вычислять минимальное значение по всем процессам с помощью вызова<reduce><random_min.cpp>:

#include <boost/mpi.hpp>
#include <iostream>
#include <cstdlib>
namespace mpi = boost::mpi;
int main()
{
  mpi::environment env;
  mpi::communicator world;
  std::srand(time(0) + world.rank());
  int my_number = std::rand();
  if (world.rank() == 0) {
    int minimum;
    reduce(world, my_number, minimum, mpi::minimum<int>(), 0);
    std::cout << "The minimum value is " << minimum << std::endl;
  } else {
    reduce(world, my_number, mpi::minimum<int>(), 0);
  }
  return 0;
}

Использование<mpi::minimum<int>>указывает на то, что необходимо вычислить минимальное значение.<mpi::minimum<int>>— двоичный функциональный объект, который сравнивает свои два параметра через<<>и возвращает меньшее значение. Любая ассоциативная двоичная функция или объект функции будет работать. Например, для сопряжения строк с<reduce>можно использовать объект функции<std::plus<std::string>><string_cat.cpp>:

#include <boost/mpi.hpp>
#include <iostream>
#include <string>
#include <functional>
#include <boost/serialization/string.hpp>
namespace mpi = boost::mpi;
int main()
{
  mpi::environment env;
  mpi::communicator world;
  std::string names[10] = { "zero ", "one ", "two ", "three ",
                            "four ", "five ", "six ", "seven ",
                            "eight ", "nine " };
  std::string result;
  reduce(world,
         world.rank() < 10? names[world.rank()]
                          : std::string("many "),
         result, std::plus<std::string>(), 0);
  if (world.rank() == 0)
    std::cout << "The result is " << result << std::endl;
  return 0;
}

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

The result is zero one two three four five six

Любой вид объектов двоичной функции может быть использован с<reduce>. Например, и таких объектов функций много в заголовке C++<<functional>>и Boost. Заголовок MPI<<boost/mpi/operations.hpp>>. Вы можете создать свой собственный объект. Функциональные объекты, используемые с<reduce>, должны быть ассоциативными, то есть<f(x, f(y,z))>должны быть эквивалентны<f(f(x,y),z)>. Если они также являются коммутативными (т.е.<f(x,y)==f(y, x)>), то они увеличиваются. MPI может использовать более эффективную реализацию<reduce>. Чтобы утверждать, что объект функции коммутативный, нужно будет специализировать класс<is_commutative>. Например, мы могли бы изменить предыдущий пример, сказав Boost. MPI, что конкатенация струн является коммутативной:

namespace boost { namespace mpi {
  template<>
  struct is_commutative<std::plus<std::string>, std::string>
    : mpl::true_ { };
} } // end namespace boost::mpi

Добавляя этот код до<main()>, увеличить. MPI предполагает, что конкатенация строк является коммутативной и использует другой параллельный алгоритм для операции<reduce>. Используя этот алгоритм, программа выводит следующее при запуске с семью процессами:

The result is zero one four five six two three

Обратите внимание, что числа в результирующей строке находятся в другом порядке: это прямой результат Boost. MPI переупорядочение операций. Результат в этом случае отличался от некоммутативного результата, потому что конкатенация строк не коммутативна:<f("x", "y")>не то же самое, что<f("y", "x")>, потому что порядок аргументов имеет значение. Для действительно коммутативных операций (например, целочисленного сложения) более эффективный коммутативный алгоритм даст тот же результат, что и некоммутативный алгоритм. Повышаю. MPI также выполняет прямые отображения от объектов функций в<<functional>>до<MPI_Op>значениях, предварительно определенных MPI (например,<MPI_SUM>,<MPI_MAX>); если у вас есть собственные объекты функций, которые могут воспользоваться этим отображением, см. шаблон классов<is_mpi_op>.

Как и<gather>,<reduce>имеет вариант «все», называемый<all_reduce>, который выполняет операцию восстановления и передает результат во все процессы. Этот вариант полезен, например, при установлении глобальных минимальных или максимальных значений.

Следующий код<global_min.cpp>показывает вещательную версию примера<random_min.cpp>:

#include <boost/mpi.hpp>
#include <iostream>
#include <cstdlib>
namespace mpi = boost::mpi;
int main(int argc, char* argv[])
{
  mpi::environment env(argc, argv);
  mpi::communicator world;
  std::srand(world.rank());
  int my_number = std::rand();
  int minimum;
  all_reduce(world, my_number, minimum, mpi::minimum<int>());
  if (world.rank() == 0) {
      std::cout << "The minimum value is " << minimum << std::endl;
  }
  return 0;
}

В этом примере мы предоставляем как входные, так и выходные значения, требующие в два раза больше места, что может быть проблемой в зависимости от размера передаваемых данных. Если нет необходимости сохранять входное значение, выходное значение может быть опущено. В этом случае входное значение будет переопределено с выходным значением и повышением. MPI способен в некоторых ситуациях реализовать операцию с более эффективным решением (используя флаг<MPI_IN_PLACE>отображения MPI C), как в следующем примере<in_place_global_min.cpp>:

#include <boost/mpi.hpp>
#include <iostream>
#include <cstdlib>
namespace mpi = boost::mpi;
int main(int argc, char* argv[])
{
  mpi::environment env(argc, argv);
  mpi::communicator world;
  std::srand(world.rank());
  int my_number = std::rand();
  all_reduce(world, my_number, mpi::minimum<int>());
  if (world.rank() == 0) {
      std::cout << "The minimum value is " << my_number << std::endl;
  }
  return 0;
}

Managing communicators

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

При инициализации среды MPI доступен только «мировой» коммуникатор (названный<MPI_COMM_WORLD>в MPI C и связываниях Фортрана). «Мировой» коммуникатор, доступ к которому осуществляется посредством построения объекта по умолчанию<mpi::communicator>, содержит все процессы MPI, присутствующие при запуске программы. Другие коммуникаторы могут быть построены путем дублирования или построения подмножеств коммуникатора «мир». Например, в следующей программе мы разделили процессы на две группы: одна для процессов, генерирующих данные, а другая для процессов, которые будут собирать данные.<generate_collect.cpp>

#include <boost/mpi.hpp>
#include <iostream>
#include <cstdlib>
#include <boost/serialization/vector.hpp>
namespace mpi = boost::mpi;
enum message_tags {msg_data_packet, msg_broadcast_data, msg_finished};
void generate_data(mpi::communicator local, mpi::communicator world);
void collect_data(mpi::communicator local, mpi::communicator world);
int main()
{
  mpi::environment env;
  mpi::communicator world;
  bool is_generator = world.rank() < 2 * world.size() / 3;
  mpi::communicator local = world.split(is_generator? 0 : 1);
  if (is_generator) generate_data(local, world);
  else collect_data(local, world);
  return 0;
}

Когда коммуникаторы разделены таким образом, их процессы сохраняют членство как в исходном коммуникаторе (который не изменяется расколом), так и в новом коммуникаторе. Однако ряды процессов могут отличаться от одного коммуникатора к другому, поскольку значения ранга в коммуникаторе всегда являются смежными значениями, начинающимися с нуля. Первые две трети процессов становятся «генераторами», а остальные — «коллекционерами». Разряды «коллекторов» в<world>коммуникаторе будут 2/3<world.size()>и выше, тогда как разряды тех же коллекторских процессов в<local>коммуникаторе начнутся с нуля. Следующий отрывок из<collect_data()>(в<generate_collect.cpp>) иллюстрирует, как управлять несколькими коммуникаторами:

mpi::status msg = world.probe();
if (msg.tag() == msg_data_packet) {
  // Receive the packet of data
  std::vector<int> data;
  world.recv(msg.source(), msg.tag(), data);
  // Tell each of the collectors that we'll be broadcasting some data
  for (int dest = 1; dest < local.size(); ++dest)
    local.send(dest, msg_broadcast_data, msg.source());
  // Broadcast the actual data.
  broadcast(local, data, 0);
}

Код в этом за исключением выполняется "мастер" коллектор, например, узел с рангом 2/3<world.size()>в<world>коммуникатор и рангом 0 в<local>(коллектор) коммуникатор. Он получает сообщение от генератора через коммуникатор<world>, а затем передает сообщение каждому из коллекторов через коммуникатор<local>.

Для большего контроля при создании коммуникаторов для подгрупп процессов, Усиление. MPI<group>предоставляет возможности для вычисления союза<|>, пересечения<&>и разности<->двух групп, генерации произвольных подгрупп и т.д.

Separating structure from content

При передаче типов данных по MPI, которые не являются фундаментальными для MPI (например, строки, списки и типы данных, определяемые пользователем), увеличить. MPI должен сначала сериализовать эти типы данных в буфер, а затем передать их; приемник затем копирует результаты в буфер, прежде чем десериализировать в объект на другом конце. Для некоторых типов данных эти накладные расходы могут быть устранены с помощью<is_mpi_datatype>. Однако типы данных переменной длины, такие как строки и списки, не могут быть типами данных MPI.

Повышаю. MPI поддерживает второй метод повышения производительности путем отделения структуры этих структур данных переменной длины от содержимого, хранящегося в структурах данных. Эта функция полезна только тогда, когда форма структуры данных остается прежней, но содержание структуры данных должно быть сообщено несколько раз. Например, при анализе конечных элементов структура сетки может быть зафиксирована в начале вычисления, но различные переменные на ячейках сетки (температура, напряжение и т.д.) будут сообщаться много раз в процессе итеративного анализа. В данном случае – рост. MPI позволяет сначала отправить «скелет» сетки один раз, затем передать «контент» несколько раз. Поскольку содержание не должно содержать никакой информации о структуре типа данных, оно может передаваться без создания отдельных буферов связи.

Чтобы проиллюстрировать использование скелетов и контента, мы возьмем несколько более ограниченный пример, в котором мастер-процесс генерирует случайные последовательности чисел в список и передает их нескольким рабским процессам. Длина списка будет фиксирована при запуске программы, поэтому содержание списка (т.е. текущая последовательность чисел) может быть эффективно передано. Полный пример приведен в<example/random_content.cpp>. Мы находимся в процессе мастера (ранг 0), который строит список, передает его структуру через<skeleton>, а затем неоднократно генерирует случайные последовательности чисел, которые будут транслироваться на процессы раба через<content>:

// Generate the list and broadcast its structure
std::list<int> l(list_len);
broadcast(world, mpi::skeleton(l), 0);
// Generate content several times and broadcast out that content
mpi::content c = mpi::get_content(l);
for (int i = 0; i < iterations; ++i) {
  // Generate new random values
  std::generate(l.begin(), l.end(), &random);
  // Broadcast the new content of l
  broadcast(world, c, 0);
}
// Notify the slaves that we're done by sending all zeroes
std::fill(l.begin(), l.end(), 0);
broadcast(world, c, 0);

Рабские процессы имеют очень похожую структуру с хозяином. Они получают (по вызову<broadcast()>) скелет структуры данных, а затем используют его для построения собственных списков целых чисел. В каждой итерации они получают через другой<broadcast()>новый контент в структуре данных и вычисляют некоторое свойство данных:

// Receive the content and build up our own list
std::list<int> l;
broadcast(world, mpi::skeleton(l), 0);
mpi::content c = mpi::get_content(l);
int i = 0;
do {
  broadcast(world, c, 0);
  if (std::find_if
       (l.begin(), l.end(),
        std::bind1st(std::not_equal_to<int>(), 0)) == l.end())
    break;
  // Compute some property of the data.
  ++i;
} while (true);

Скелеты и содержимое любого типа данных Serializable могут передаваться либо через<send>и<recv>членов класса<communicator>(для точечных коммуникаторов), либо транслироваться через<broadcast()>коллектив. При разделении структуры данных на скелет и содержимое будьте осторожны, чтобы не изменять структуру данных (либо на стороне отправителя, либо на стороне получателя), не передавая скелет снова. Повышаю. MPI не может обнаружить эти случайные изменения структуры данных, которые, вероятно, приведут к неправильной передаче данных или нестабильным программам.

Performance optimizations

Serialization optimizations

Для получения оптимальной производительности для малых типов данных фиксированной длины, не содержащих каких-либо указателей, очень важно отметить их с помощью признаков типа Boost. MPI и Boost.Serialization.

Уже обсуждалось, что фиксированные типы длины, не содержащие указателей, могут использоваться в качестве<is_mpi_datatype>, например:

namespace boost { namespace mpi {
  template <>
  struct is_mpi_datatype<gps_position> : mpl::true_ { };
} }

или эквивалентный макрос

BOOST_IS_MPI_DATATYPE(gps_position)

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

BOOST_CLASS_TRACKING(gps_position,track_never)
BOOST_CLASS_IMPLEMENTATION(gps_position,object_serializable)

Homogeneous Machines

Дополнительные оптимизации возможны на однородных машинах, избегая вызовов MPI_Pack/MPI_Unpack, но используя прямую битовую копию. Эта функция включена по умолчанию путем определения макроса<BOOST_MPI_HOMOGENEOUS>в файле включения<boost/mpi/config.hpp>. Это определение должно быть последовательным при строительстве. MPI и при создании приложения.

Кроме того, все классы должны быть помечены как is_mpi_datatype, так и as is_bitwise_serializable с помощью вспомогательного макроса Boost. Сериализация:

BOOST_IS_BITWISE_SERIALIZABLE(gps_position)

Обычно безопасно сериализовать класс, для которого является_mpi_datatype, используя двоичную копию битов. Исключение составляют классы, для которых некоторые члены должны быть пропущены для сериализации.

Mapping from C MPI to Boost.MPI

В этом разделе представлены таблицы, которые отображают функции и константы стандартного C MPI до их увеличения. эквиваленты MPI. Он будет наиболее полезен для пользователей, которые уже знакомы с интерфейсами C или Fortran для MPI, или для переноса существующих параллельных программ на Boost. MPI.

Table 24.1. Point-to-point communication

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_ANY_SOURCE>

<any_source>

<MPI_ANY_TAG>

<any_tag>

<MPI_Bsend>

без поддержки

<MPI_Bsend_init>

без поддержки

<MPI_Buffer_attach>

без поддержки

<MPI_Buffer_detach>

без поддержки

<MPI_Cancel>

<request::cancel>

<MPI_Get_count>

<status::count>

<MPI_Ibsend>

без поддержки

<MPI_Iprobe>

<communicator::iprobe>

<MPI_Irsend>

без поддержки

<MPI_Isend>

<communicator::isend>

<MPI_Issend>

без поддержки

<MPI_Irecv>

<communicator::irecv>

<MPI_Probe>

<communicator::probe>

<MPI_PROC_NULL>

без поддержки

<MPI_Recv>

<communicator::recv>

<MPI_Recv_init>

без поддержки

<MPI_Request_free>

без поддержки

<MPI_Rsend>

без поддержки

<MPI_Rsend_init>

без поддержки

<MPI_Send>

<communicator::send>

<MPI_Sendrecv>

без поддержки

<MPI_Sendrecv_replace>

без поддержки

<MPI_Send_init>

без поддержки

<MPI_Ssend>

без поддержки

<MPI_Ssend_init>

без поддержки

<MPI_Start>

без поддержки

<MPI_Startall>

без поддержки

<MPI_Test>

<request::test>

<MPI_Testall>

<test_all>

<MPI_Testany>

<test_any>

<MPI_Testsome>

<test_some>

<MPI_Test_cancelled>

<status::cancelled>

<MPI_Wait>

<request::wait>

<MPI_Waitall>

<wait_all>

<MPI_Waitany>

<wait_any>

<MPI_Waitsome>

<wait_some>


Повышаю. MPI автоматически отображает типы данных C и C++ на их эквиваленты MPI. Следующая таблица иллюстрирует отображения между типами C++ и константами типа данных MPI.

Table 24.2. Datatypes

Константа

Повышаю. эквивалент MPI

<MPI_CHAR>

<signedchar>

<MPI_SHORT>

<signedshort int>

<MPI_INT>

<signedint>

<MPI_LONG>

<signedlong int>

<MPI_UNSIGNED_CHAR>

<unsignedchar>

<MPI_UNSIGNED_SHORT>

<unsignedshort int>

<MPI_UNSIGNED_INT>

<unsignedint>

<MPI_UNSIGNED_LONG>

<unsignedlong int>

<MPI_FLOAT>

<float>

<MPI_DOUBLE>

<double>

<MPI_LONG_DOUBLE>

<longdouble>

<MPI_BYTE>

неиспользованные

<MPI_PACKED>

используется длясерийных типов данных

<MPI_LONG_LONG_INT>

<longlong int>, если поддерживается компилятором

<MPI_UNSIGNED_LONG_LONG_INT>

<unsignedlong longint>, если поддерживается компилятором

<MPI_FLOAT_INT>

<std::pair<float, int>>

<MPI_DOUBLE_INT>

<std::pair<double, int>>

<MPI_LONG_INT>

<std::pair<long, int>>

<MPI_2INT>

<std::pair<int,int>>

<MPI_SHORT_INT>

<std::pair<short, int>>

<MPI_LONG_DOUBLE_INT>

<std::pair<longdouble,int>>


Повышаю. MPI не предоставляет прямые обертки для функциональности MPI-производных типов данных. Вместо этого, буст. MPI опирается на библиотекуBoost.Serializationдля построения типов данных MPI для классов, определенных пользователем. В разделе отипах данных, определяемых пользователем, описан этот механизм, который используется для типов, помеченных как «типы данных MPI» с использованием<is_mpi_datatype>.

Приведенная ниже таблица типов данных описывает, какие типы C++ соответствуют функциональности конструктора типов данных C MPI. Повышаю. MPI может фактически не использовать функцию C MPI, указанную при построении типов данных определенной формы. Так как фактические типы данных построены Boost. MPI обычно скрыты от пользователя, многие из этих операций называются внутри Boost. MPI.

Table 24.3. Derived datatypes

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_Address>

Используется автоматически. MPI для MPI версии 1.x

<MPI_Get_address>

используется автоматически. MPI версии 2.0 и выше

<MPI_Type_commit>

Используется автоматически. MPI

<MPI_Type_contiguous>

массивы

<MPI_Type_extent>

Используется автоматически. MPI

<MPI_Type_free>

Используется автоматически. MPI

<MPI_Type_hindexed>

Любой тип, используемый в качестве подобъекта

<MPI_Type_hvector>

неиспользованные

<MPI_Type_indexed>

Любой тип, используемый в качестве подобъекта

<MPI_Type_lb>

без поддержки

<MPI_Type_size>

Используется автоматически. MPI

<MPI_Type_struct>

пользовательские классы и структуры с MPI 1.x

<MPI_Type_create_struct>

пользовательские классы и структуры с MPI 2.0 и выше

<MPI_Type_ub>

без поддержки

<MPI_Type_vector>

Используется автоматически. MPI


Средства упаковки MPI хранят значения в смежный буфер, который позже может быть передан через MPI и распакован в отдельные значения через средства распаковки MPI. Как и в случае с типом данных, Boost. MPI обеспечивает абстрактный интерфейс к средствам упаковки и распаковки MPI. В частности, два класса архивов<packed_oarchive>и<packed_iarchive>могут использоваться для упаковки или распаковки смежного буфера с использованием средств MPI.

Table 24.4. Packing and unpacking

Функция C

Повышаю. эквивалент MPI

<MPI_Pack>

<packed_oarchive>

<MPI_Pack_size>

используется внутри Boost.MPI

<MPI_Unpack>

<packed_iarchive>


Повышаю. MPI поддерживает индивидуальное отображение для большинства коллективов MPI. Для каждого коллектива, предоставленного Boost. MPI, базовый коллектив C MPI будет использоваться, когда это возможно (и эффективно).

Table 24.5. Collectives

Функция C

Повышаю. эквивалент MPI

<MPI_Allgather>

<all_gather>

<MPI_Allgatherv>

Большинство применений поддерживается<all_gather>

<MPI_Allreduce>

<all_reduce>

<MPI_Alltoall>

<all_to_all>

<MPI_Alltoallv>

Большинство применений поддерживается<all_to_all>

<MPI_Barrier>

<communicator::barrier>

<MPI_Bcast>

<broadcast>

<MPI_Gather>

<gather>

<MPI_Gatherv>

большинство применений, поддерживаемых<gather>, другие применения, поддерживаемые<gatherv>

<MPI_Reduce>

<reduce>

<MPI_Reduce_scatter>

без поддержки

<MPI_Scan>

<scan>

<MPI_Scatter>

<scatter>

<MPI_Scatterv>

большинство применений, поддерживаемых<scatter>, другие применения, поддерживаемые<scatterv>

<MPI_IN_PLACE>

поддерживается косвенно<all_reduce by omitting the output value>


Повышаю. MPI использует функциональные объекты, чтобы указать, как должны происходить сокращения в его эквивалентах<MPI_Allreduce>,<MPI_Reduce>и<MPI_Scan>. В следующей таблице показано, какпредопределенныеипользовательские операции сокращениямогут быть отображены между C MPI и Boost. MPI.

Table 24.6. Reduction operations

Константа

Повышаю. эквивалент MPI

<MPI_BAND>

<bitwise_and>

<MPI_BOR>

<bitwise_or>

<MPI_BXOR>

<bitwise_xor>

<MPI_LAND>

<std::logical_and>

<MPI_LOR>

<std::logical_or>

<MPI_LXOR>

<logical_xor>

<MPI_MAX>

<maximum>

<MPI_MAXLOC>

без поддержки

<MPI_MIN>

<minimum>

<MPI_MINLOC>

без поддержки

<MPI_Op_create>

используется внутри Boost.MPI

<MPI_Op_free>

используется внутри Boost.MPI

<MPI_PROD>

<std::multiplies>

<MPI_SUM>

<std::plus>


MPI определяет несколько специальных коммуникаторов, включая<MPI_COMM_WORLD>(включая все процессы, с которыми может связываться локальный процесс),<MPI_COMM_SELF>(включая только локальный процесс) и<MPI_COMM_EMPTY>(включая отсутствие процессов). Все эти специальные коммуникаторы являются экземплярами класса<communicator>в Boost. MPI.

Table 24.7. Predefined communicators

Константа

Повышаю. эквивалент MPI

<MPI_COMM_WORLD>

построенный по умолчанию<communicator>

<MPI_COMM_SELF>

a<communicator>, который содержит только текущий процесс

<MPI_COMM_EMPTY>

a<communicator>, который оценивает ложные


Повышаю. MPI поддерживает группы процессов через свой класс<group>.

Table 24.8. Group operations and constants

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_GROUP_EMPTY>

построенный по умолчанию<group>

<MPI_Group_size>

<group::size>

<MPI_Group_rank>

Memberref boost::mpi::group::rank<group::rank>

<MPI_Group_translate_ranks>

Memberref boost::mpi::group::translate_ranks<group::translate_ranks>

<MPI_Group_compare>

операторов<==>и<!=>

<MPI_IDENT>

операторов<==>и<!=>

<MPI_SIMILAR>

операторов<==>и<!=>

<MPI_UNEQUAL>

операторов<==>и<!=>

<MPI_Comm_group>

<communicator::group>

<MPI_Group_union>

оператор<|>для групп

<MPI_Group_intersection>

оператор<&>для групп

<MPI_Group_difference>

оператор<->для групп

<MPI_Group_incl>

<group::include>

<MPI_Group_excl>

<group::exclude>

<MPI_Group_range_incl>

без поддержки

<MPI_Group_range_excl>

без поддержки

<MPI_Group_free>

Используется автоматически. MPI


Повышаю. MPI обеспечивает манипулирование коммуникаторами через класс<communicator>.

Table 24.9. Communicator operations

Функция C

Повышаю. эквивалент MPI

<MPI_Comm_size>

<communicator::size>

<MPI_Comm_rank>

<communicator::rank>

<MPI_Comm_compare>

операторов<==>и<!=>

<MPI_Comm_dup>

<communicator>Конструктор классов с использованием<comm_duplicate>

<MPI_Comm_create>

<communicator>Конструктор

<MPI_Comm_split>

<communicator::split>

<MPI_Comm_free>

Используется автоматически. MPI


Повышаю. MPI в настоящее время обеспечивает поддержку межкоммуникаторов через класс 1110.

Table 24.10. Inter-communicator operations


Повышаю. В настоящее время MPI не поддерживает кэширование атрибутов.

Table 24.11. Attributes and caching

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_NULL_COPY_FN>

без поддержки

<MPI_NULL_DELETE_FN>

без поддержки

<MPI_KEYVAL_INVALID>

без поддержки

<MPI_Keyval_create>

без поддержки

<MPI_Copy_function>

без поддержки

<MPI_Delete_function>

без поддержки

<MPI_Keyval_free>

без поддержки

<MPI_Attr_put>

без поддержки

<MPI_Attr_get>

без поддержки

<MPI_Attr_delete>

без поддержки


Повышаю. MPI обеспечит полную поддержку для создания коммуникаторов с различными топологиями и последующего запроса этих топологий. Поддержка топологий графов обеспечивается через интерфейс кBoost Graph Library (BGL), где может быть создан коммуникатор, который соответствует структуре любого графа BGL, а топология графов коммуникатора может рассматриваться как граф BGL для использования в существующих общих алгоритмах графов.

Table 24.12. Process topologies

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_GRAPH>

ненужный; использование<communicator::as_graph_communicator>

<MPI_CART>

ненужный; использование<communicator::has_cartesian_topology>

<MPI_Cart_create>

без поддержки

<MPI_Dims_create>

без поддержки

<MPI_Graph_create>

<graph_communicator ctors>

<MPI_Topo_test>

<communicator::as_graph_communicator>,<communicator::has_cartesian_topology>

<MPI_Graphdims_get>

<num_vertices>,<num_edges>

<MPI_Graph_get>

<vertices>,<edges>

<MPI_Cartdim_get>

без поддержки

<MPI_Cart_get>

без поддержки

<MPI_Cart_rank>

без поддержки

<MPI_Cart_coords>

без поддержки

<MPI_Graph_neighbors_count>

<out_degree>

<MPI_Graph_neighbors>

<out_edges>,<adjacent_vertices>

<MPI_Cart_shift>

без поддержки

<MPI_Cart_sub>

без поддержки

<MPI_Cart_map>

без поддержки

<MPI_Graph_map>

без поддержки


Повышаю. MPI поддерживает экологические запросы через класс<environment>.

Table 24.13. Environmental inquiries

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_TAG_UB>

ненужный; использование<environment::max_tag>

<MPI_HOST>

ненужный; использование<environment::host_rank>

<MPI_IO>

ненужный; использование<environment::io_rank>

<MPI_Get_processor_name>

<environment::processor_name>


Повышаю. MPI переводит ошибки MPI в исключения, о которых сообщается через класс<exception>.

Table 24.14. Error handling

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_ERRORS_ARE_FATAL>

Неиспользуемые; ошибки переводятся в Рост. Исключения из MPI

<MPI_ERRORS_RETURN>

Неиспользуемые; ошибки переводятся в Рост. Исключения из MPI

<MPI_errhandler_create>

Неиспользуемые; ошибки переводятся в Рост. Исключения из MPI

<MPI_errhandler_set>

Неиспользуемые; ошибки переводятся в Рост. Исключения из MPI

<MPI_errhandler_get>

Неиспользуемые; ошибки переводятся в Рост. Исключения из MPI

<MPI_errhandler_free>

Неиспользуемые; ошибки переводятся в Рост. Исключения из MPI

<MPI_Error_string>

используется внутри Boost.MPI

<MPI_Error_class>

<exception::error_class>


Схемы MPI выставляются через Boost. Класс MPI<timer>, обеспечивающий интерфейс, совместимый с библиотекойBoost Timer.

Table 24.15. Timing facilities

С. Функция/постоянство

Повышаю. эквивалент MPI

<MPI_WTIME_IS_GLOBAL>

ненужный; использование<timer::time_is_global>

<MPI_Wtime>

использовать<timer::elapsed>для определения времени, прошедшего от определенной начальной точки

<MPI_Wtick>

<timer::elapsed_min>


Запуск и остановка MPI управляются строительством и разрушением Boost. Класс MPI<environment>.

Table 24.16. Startup/shutdown facilities

Функция C

Повышаю. эквивалент MPI

<MPI_Init>

<environment>Конструктор

<MPI_Finalize>

<environment>Деструктор

<MPI_Initialized>

<environment::initialized>

<MPI_Abort>

<environment::abort>


Повышаю. MPI не обеспечивает поддержку профилирующих объектов в MPI 1.1.

Table 24.17. Profiling interface

Функция C

Повышаю. эквивалент MPI

<PMPI_*>рутины

без поддержки

<MPI_Pcontrol>

без поддержки



PrevUpHomeNext

Статья Tutorial раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 24. Boost.MPI может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Chapter 24. Boost.MPI ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 17:47:09/0.048208951950073/1