MPI 共享内存和四精度的分段错误

Segmentation fault with MPI shared memory and quad precision

当四倍精度 (__float128) 与 GCC(但不是英特尔 C 编译器)一起使用时,一个使用 MPI 共享内存的非常简单的 C 程序对我来说崩溃了。该程序为每个 MPI 进程分配一个一个元素长的共享实数组(分配由主进程完成,然后由其他进程访问)。每个进程写入一个元素并将另一个元素打印到标准输出。当使用标准内置类型 floatdouble(或只是单个进程)时,根本没有问题。使用 __float128(以及多个进程)时,下面突出显示的行会导致所有进程出现分段错误。

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

const int verbose = 0;

/* typedef float real; */
/* typedef double real; */
typedef __float128 real;

#define _(FUN, ARGS) \
{ \
    int status = FUN ARGS; \
    if (verbose) printf(#FUN " exit code: %d\n", status); \
}

int main (int argc, char* argv[])
{
    int nprocs, rank, assert = 0, disp = 1, master = 0, bytesize = sizeof(real);
    void* baseptr = NULL;
    real* arr;
    MPI_Win win;

    printf("Data type byte size: %d\n", bytesize);

    _(MPI_Init,      (&argc, &argv))
    _(MPI_Comm_size, (MPI_COMM_WORLD, &nprocs))
    _(MPI_Comm_rank, (MPI_COMM_WORLD, &rank))

    MPI_Aint totsize = rank == 0 ? bytesize * nprocs : 0;

    _(MPI_Win_allocate_shared, (totsize, disp, MPI_INFO_NULL, MPI_COMM_WORLD, &baseptr, &win))
    _(MPI_Win_shared_query,    (win, master, &totsize, &disp, &arr))
    _(MPI_Win_fence,           (assert, win))

    arr[rank] = rank + 1;  /*  <-- Segmentation fault here when using __float128 */

    _(MPI_Win_fence, (assert, win))

    printf("Element %d: %g\n", (rank + 1) % nprocs, (double)arr[(rank + 1) % nprocs]);

    _(MPI_Win_fence, (assert, win))
    _(MPI_Win_free,  (&win));
    _(MPI_Finalize,  ());

    return 0;
}

该程序在某些配置下有效,但在其他配置下无效。以下是我能够检查的那些:

Compiler Open MPI 4.1.0 Intel MPI (oneAPI 2021)
Intel oneAPI 2021 mpiicc OK for all types OK for all types
GCC 9.3 mpicc FAILS with __float128 N/A
GCC 10.2 mpicc FAILS with __float128 N/A

我用的是 openSUSE Tumbleweed 20210319.

乍一看,这似乎是 GCC 中的一个问题。还是我在代码中遗漏了什么?

这里发生的是 MPI_Win_allocate_shared() returns 一个按 8 字节对齐的内存区域,但是 GCC 假设 arr 按 16 字节对齐,如果 arr 未按 16 字节对齐。

您可以通过手动重新对齐数据来解决此问题。

#define REALIGN(a, type) \
    ((a) + sizeof(type) - 1 & ~(sizeof(type) - 1))

然后

    MPI_Aint totsize = rank == 0 ? (bytesize * (nprocs+1)) : 0;

最后

    arr = (real *)REALIGN((unsigned long)arr, real);

FWIW,在最近的英特尔处理器上,对齐和非对齐指令 运行 在数据对齐时以相同的速度,这可能是英特尔编译器不再生成对齐指令的原因。