在同一程序中使用 OpenMP 和 MPI

Using OpenMP and MPI in the same program

我目前正在为我的并行编程 class 分配任务,我需要按顺序编写相同的程序,然后使用 OpenMP 并行化,然后使用 MPI 并行化。

对于上下文,作业是关于在随机字符矩阵中搜索回文。我已经让大部分代码正常工作,我的问题是关于如何构建、编译和 运行 项目。

我可以创建三个单独的程序并 运行 它们独立,但我想将它们全部组合在同一个项目中,这样三个版本 运行 一个接一个地在同一个首字母上矩阵。这使我可以对每个版本进行计时以进行比较。

我正在使用 CMake 作为构建工具。

我的 CMakeLists.txt :

cmake_minimum_required(VERSION 3.21)
project(<project-name> C)

set(CMAKE_C_STANDARD 23)

find_package(MPI REQUIRED)
include_directories(SYSTEM ${MPI_INCLUDE_PATH})

find_package(OpenMP REQUIRED)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")

add_executable(<project-name> <source-files>)
target_link_libraries(<project-name> ${MPI_C_LIBRARIES})

我使用以下命令构建项目

mkdir build && cd build && cmake .. && make

我的主要功能:

// All the header inclusions

int main(int argc, char **argv) {

    // Initialisation.
    srand(time(NULL));
    omp_set_num_threads(omp_get_num_procs());
    double start_time;
    ushort number_of_palindromes = 0;
    ushort palindrome_length = 5;
    ushort rows = 25000;
    ushort cols = 25000;
    char **matrix = create_matrix_of_chars(rows, cols);
    printf("Matrix of size %dx%d, searching for palindromes of size %d.\n", rows, cols, palindrome_length);

    // Run sequentially.
    printf("%-45s", "Running sequentially ... ");
    start_time = omp_get_wtime();
    number_of_palindromes = find_palindromes_sequentially(matrix, rows, cols, palindrome_length);
    printf("Found %4d palindromes in %7.4f seconds.\n", number_of_palindromes, omp_get_wtime() - start_time);

    // Run using OpenMP.
    printf("Running with OpenMP on %d %-20s", omp_get_num_procs(), "threads ... ");
    start_time = omp_get_wtime();
    number_of_palindromes = find_palindromes_using_openmp(matrix, rows, cols, palindrome_length);
    printf("Found %4d palindromes in %7.4f seconds.\n", number_of_palindromes, omp_get_wtime() - start_time);

    // Run using MPI.
    int num_procs, rank;
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    printf("%d: hello (p=%d)\n", rank, num_procs);
    MPI_Finalize();

    // Cleanup and exit.
    free_matrix(matrix, rows);
    return 0;

}

当 运行宁 ./<project-name> 顺序和 OpenMP 版本 运行 一个接一个正确。但是,当 运行ning mpirun --use-hwthread-cpus ./<project-name> 程序启动整个项目的 8 个实例时(“大小矩阵...”行被打印 8 次)。

我的理解是 MPI 区域由 MPI_Init(...)MPI_Finalize() 分隔,但事实似乎并非如此。我将如何解决这个问题?

提前感谢您的回答。

没有“MPI 区域”这样的东西。 MPI 使用仅 communicate/synchronize 通过网络的独立进程。意思是:整个可执行文件 运行 在你启动它的实例中。每个语句,甚至在 MPI_Init 被每个实例执行之前。

I need to write the same program sequentially, then parallelized using OpenMP then parallelized using MPI.

这可能有几个含义:

  • 您正在为这三个版本一个一个地创建三个单独的 程序。 (我知道这是您要避免的,但是 请继续阅读 ,因为如果对这三种方法进行 time-based 比较,这就是您实际想要的你在找)
  • 您首先使用 OpenMP 创建顺序程序的并行版本,然后使用 MPI,反之亦然,但两者是分开的。第一种方法与这种方法的区别在于,如果您想在程序内部进行 'sequential vs parallel' 比较,则可以在这些并行化版本中单独使用顺序代码,因为那样不会太不准确。例如,您可以在 MPI_Init 之前使用序列化代码 运行ning(例如,您可以使用 MPI 使等级为 0 运行 的进程成为程序中的顺序版本,但这会导致并行化代码的时间不准确,以及 0 级的重负载不平衡做更多的工作)对于你的 MPI 版本,并且没有 OpenMP 指令用于一个单独的代码实例,无论你试图在你的程序中并行化使用 OpenMP。
  • 您正在创建一个同时具有多个 MPI 等级和 OpenMP 线程的程序,即它使用混合设置,这更复杂并且需要适当的设置。如果您也想在此处使用顺序版本,请使用我上面提到的方法,但结合两者,即在初始化 MPI 环境之前,不使用 OpenMP 指令和子句。

根据您真正想要实现的目标,您将需要思考和调整您的代码(为第三种情况添加配置,我很快就会谈到)。

I could create three separate programs and run them independently, but I would like to combine them all in the same project so the three versions run one after the other and on the same initial matrix. This allows me to time each version to compare them.

坏主意。多个 MPI 进程将 运行 连接您的程序,并且您不会获得线程分担工作的准确时间(即假设即使您的代码与您获得的设置一起工作)。 (假设您打算为 shared-memory 并行性的组件计时,即线程而不是进程,假设您使用的是 omp_get_wtime()

为了使每种方法的实际时间最接近,理想情况下,您希望将这三个程序分开并分别为它们计时,或者使用一些内部函数调用,例如 MPI_Wtime()omp_get_wtime() 用于 MPI 等级(可能希望在此基础上进行减少,例如 MPI_MAX)和 OpenMP 线程,分别为 使用一些可以测量时间的外部工具,例如 perf (如果您想为整个程序而不是代码中的特定部分计时,这是理想的选择),您可以将其合并到您的二进制执行语句中生成文件。

但是,如果您真的想要同时使用两者并进行混合设置,那么您必须在调用 MPI_Init(),指定您想要的 multi-threading(三个选项)处理流程的方式。有关这些级别的更多信息,请检查 documentation 中您想要使用的选项。 (对于你的情况,我会推荐漏斗配置)

你也可以用一种巧妙的方式编写你的程序,将所有三种方法都放在一个 #if parameterName==<value> ... #endif 块中,以便 运行 只有一种方法(或者可能两种,取决于你如何去做)一次,基于你在编译时指定和设置的参数(对不同的块有不同的值)(将 -D<parameterName>=<value> 添加到你的其他编译标志)。