新的运算符+ OpenMP动态调度子句

时间:2014-04-23 14:25:42

标签: c++ arrays multithreading openmp dynamic-memory-allocation

我一直在使用C ++代码来执行量子化学,原子和分子任务,其中暗示了大量的数组工作(1D,2D,3D等)。我有一个名为array的整个班级来处理这个问题。当然,从最开始,最基本的成员函数是那些为这些数组动态分配内存,调整大小或删除它们的函数。

data = new double **[row]();
#pragma omp for schedule(static) nowait
for(unsigned int i = 0; i < row; ++i)
{
    data[i] = new double *[column]();
}

现在我正在做的是使用OpenMP加速这些例程。对于大部分例程,我一直在使用schedule(static) nowait clasues将我的循环划分为step/threads的块,因为这些块几乎花费了相同的时间来处理它们的线程。

但是对于像上面这样的循环,通过几次调用new运算符,我感觉(糟糕)感觉这些循环的块不会花费相同的时间在它们的线程中执行,在某种意义上我应该考虑改为应用schedule(dynamic, chunk_size)

你们同意吗?动态分配不是那么简单的任务,而且可能很昂贵,因此动态分配的大块可能在执行时间上有所不同。

实际上我不确定我是否在堆栈碎片或类似的事情上没有做任何错误。建议是值得欢迎的。

PS:我正在使用nowait子句来尽量减少隐含障碍的瓶颈。

3 个答案:

答案 0 :(得分:0)

如果您使用默认的new运算符,则您的特定循环可能不会提供很多并行机会,因为堆是单个资源,并且需要通过互斥锁序列化对它的访问。但是,假设您有其他要使用OpenMP的循环,以下内容应该有所帮助。

来自OpenMP 3.1 specification

  

静态当指定计划静态 chunk_size )时,迭代会被分成大小为<的块em> chunk_size ,并按照线程编号的顺序以循环方式将组块分配给团队中的线程。

     

当没有指定 chunk_size 时,迭代空间被划分为大小大致相等的块,并且最多一个块被分配给每个线程。请注意,在这种情况下,未指定块的大小。

     

动态当指定计划动态 chunk_size )时,迭代次数为   当线程请求时,以块的形式分发给团队中的线程。每个线程执行一个迭代块,然后请求另一个块,直到没有剩余的块要分发。

     

每个块包含 chunk_size 迭代,除了要分发的最后一个块,其迭代次数可能更少。

     

如果未指定 chunk_size ,则默认为1.

在您的情况下,您没有指定 chunk_size ,因此未指定每个任务的迭代次数。

通常,我更喜欢控制线程数和每个任务执行的迭代次数。我发现(在Windows上,用mingw-w64编译),任务开始一大堆工作会有很大的开销,因此将它们作为一个大块给出它是有益的。我倾向于使用动态(虽然我可以将静态用于固定执行时间任务),并将 chunk_size 设置为循环计数除以线程数。在您的情况下,如果您怀疑任务执行时间不均匀,则可以将其除以2或4。

// At the top of a C++ file:
static int NUM_THREADS = omp_get_num_procs();

// Then for your loop construct (I'm using a combined parallel for here):
#pragma omp parallel for num_threads(NUM_THREADS) \
   schedule(dynamic, row / NUM_THREADS / 2)
for(unsigned int i = 0; i < row; ++i)
{
   data[i] = new double *[column]();
}

另请注意,如果您未设置 num_threads ,则默认值为 nthreads-var ,该值由omp_get_max_threads确定。

关于nowait条款,显然请确保您没有在循环构造之外使用data。我正在使用上面的组合并行循环结构,这意味着无法指定nowait

答案 1 :(得分:0)

有一种更清洁的方法来完成所有这些:

std::vector<double> actual_data(omp_get_num_procs() * column);

std::vector<double *> data(omp_get_num_procs());
for (unsigned i = 0; i < row; i++) {
    data[i] = &(actual_data[i * column]);
}

现在你有一个单独的数组,一次性分配,并有一个指向该数组的指针数组。在并行算法内部,您可以使用data[i][j]来获取所需的成员,实际上只需零开销(在编译时会产生开销)。

唯一的潜在风险是false sharing,因为矩阵的行可能在其端点共享一个缓存行。

Memory management is automatic;无需free任何事情。

答案 2 :(得分:0)

如果您计划进行大量基本线性代数运算(BLAS),那么我建议您不要将数组数组用于多维数组。不幸的是,多维静态和动态数组的C语法已经破坏了对称性。这导致Java和C#使用相同的语法,以便人们经常认为,当他们分配2D数组时,他们会获得与静态数组相同的东西,但是从堆而不是堆栈。您的2D数组实际上是Jagged arrays

根据我对BLAS操作(以及图像处理)的经验,您需要连续的二维数据块内存块。因此,对于nxn矩阵,您应该像double[n*n]一样分配并将其作为data[i*n+j]访问,或者创建一个C ++矩阵类,您可以在其中访问像matrix.get(i,j)这样的矩阵。

不要并行分配内存。大多数BLAS操作无论如何都将受到内存限制,因此OpenMP仅对Level-3 BLAS操作有用,例如矩阵乘法,LU分解,cholesky分解,O(n^3)