将OpenMP减少为C ++模板指定大小的数组会导致未定义的行为

时间:2018-04-23 17:00:34

标签: c++ openmp

我是OpenMP的新手,但我正在尝试使用它来加速对具有大量行和少量列的2D数组条目的某些操作。同时,我使用简化来计算每列中所有数组值的总和。代码看起来像这样(我将在片刻解释奇怪的位):

template <unsigned int NColumns>
void Function(int n_rows, double** X, double* Y)
{
    #pragma omp parallel for reduction(+:Y[:NColumns])
    for (int r = 0; r < n_rows; ++r)
    {
        for (int c = 0; c < NColumns; ++c)
        {
            X[r][c] = some_complicated_stuff(X[r], X[r][c]);
            Y[c] += X[r][c];
        }
    }
}

为了澄清,X是在堆上分配的n_rows x NColumns大小的2D数组,Y是NColumns大小的1D数组。 some_complicated_stuff实际上并未作为单独的函数实现,但我对该行中的X[r][c]所做的只取决于X[r][c]和1D数组X[r]中的其他值。< / p>

NColumns作为模板参数而不是作为常规参数(如n_rows)传入的原因是,在编译时知道NColumns时,编译器可以在上述功能中更积极地优化内循环。我知道程序运行时NColumns将成为少数值之一,所以稍后我会有类似这样的代码:

cin >> n_cols;
double** X;
double Y[n_cols];

// initialise X and Y, etc. . .

for (int i = 0; i < n_iterations; ++i)
{
    switch (n_cols)
    {
        case  2: Function< 2>(X, Y); break;
        case 10: Function<10>(X, Y); break;
        case 60: Function<60>(X, Y); break;
        default: throw "Unsupported n_cols."; break;
    }
    // . . .
    Report(i, Y); // see GDB output below
}

通过测试,我发现将此NColumns“参数”更新为模板参数而非正常函数参数实际上会使性能明显提高。然而,我也发现,一旦在一个蓝色的月亮(比如大约每10 ^ 7次调用Function),程序就会挂起 - 更糟糕的是,它的行为有时会从程序的一次运行变为下一个。这种情况很少发生,我在隔离错误方面遇到了很多麻烦,但我现在想知道是不是因为我在OpenMP减少中使用了这个NColumns模板参数。

我注意到similar StackOverflow question询问在缩减中使用模板类型,这显然会导致未指定的行为 - OpenMP 3.0 spec

  

如果数据共享属性子句中引用的变量具有类型   从模板派生,并没有其他参考   程序中的变量,那么与该变量相关的任何行为都是   未指定的。

在这种情况下,它不是正在使用的模板类型本身,但我在同一个球场。我在这里搞砸了,或者错误更可能出现在代码的其他部分?

我正在使用GCC 6.3.0。

如果它更有帮助,这里是来自Function内部的真实代码。 X实际上是一个扁平的2D数组;其他地方定义了wwmin_x

#pragma omp parallel for reduction(+:Y[:NColumns])
for (int i = 0; i < NColumns * n_rows; i += NColumns)
{
    double total = 0;
    for (int c = 0; c < NColumns; ++c)
        if (X[i + c] > 0)
            total += X[i + c] *= ww[c];

    if (total > 0)
        for (int c = 0; c < NColumns; ++c)
            if (X[i + c] > 0)
                Y[c] += X[i + c] = (X[i + c] < min_x * total ? 0 : X[i + c] / total);
}

为了稍微加厚一下情节,我将gdb附加到程序的正在运行的程序中,这就是回溯显示给我的内容:

#0  0x00007fff8f62a136 in __psynch_cvwait () from /usr/lib/system/libsystem_kernel.dylib
#1  0x00007fff8e65b560 in _pthread_cond_wait () from /usr/lib/system/libsystem_pthread.dylib
#2  0x000000010a4caafb in omp_get_num_procs () from /opt/local/lib/libgcc/libgomp.1.dylib
#3  0x000000010a4cad05 in omp_get_num_procs () from /opt/local/lib/libgcc/libgomp.1.dylib
#4  0x000000010a4ca2a7 in omp_in_final () from /opt/local/lib/libgcc/libgomp.1.dylib
#5  0x000000010a31b4e9 in Report(int, double*) ()
#6  0x3030303030323100 in ?? ()
[snipped traces 7-129, which are all ?? ()]
#130 0x0000000000000000 in ?? ()

Report()是一个在程序主循环内调用但不在Function()内的函数(我已将其添加到上面的中间代码片段中),Report()不包含任何OpenMP pragma。这是否能说明发生了什么?

请注意,可执行文件在进程开始运行和我将GDB连接到它之间进行了更改,这需要引用新的(已更改的)可执行文件。这可能意味着符号表搞砸了。

1 个答案:

答案 0 :(得分:0)

我已经成功解决了这个问题。

其中一个问题是程序表现不确定。这只是因为(1)OpenMP执行线程完成顺序的减少,这是非确定性的,并且(2)浮点加法是非关联的。我假设减少将以线号顺序执行,但事实并非如此。因此,只要线程数大于2,即使从一次运行到下一次运行的线程数相同,任何减少使用浮点运算的OpenMP for构造都可能是不确定的。有关此问题的一些相关StackOverflow问题是herehere

另一个问题是该程序偶尔会挂起。我无法解决这个问题。在挂起进程上运行gdb始终会在堆栈跟踪的顶部生成__psynch_cvwait ()。它挂起了并行化for循环的每10 ^ 8次执行。

希望这有点帮助。