如何并行化2D时间演变

时间:2017-10-30 09:22:37

标签: c++ multithreading openmp nested-loops differential-equations

我有一个C ++代码,可以执行生活在2D空间网格上的四个变量的时间演变。为了节省一些时间,我尝试将我的代码与OpenMP并行化,但我无法让它工作:无论我使用多少核,运行时基本保持不变或增加。 (我的代码确实使用了24个核心或者我指定的多个核心,因此编译不是问题。)

我觉得单个时间步长的运行时间太短,生成线程的开销会导致潜在的加速。

我的代码布局是:

for (int t = 0; t < max_time_steps; t++) {

    // do some book-keeping
    ...

    // perform time step
    // (1) calculate righthand-side of ODE:
    for (int i = 0; i < nr; i++) {
        for (int j = 0; j < ntheta; j++) {
            rhs[0][i][j] = A0[i][j] + B0[i][j] + ...;
            rhs[1][i][j] = A1[i][j] + B1[i][j] + ...;
            rhs[2][i][j] = A2[i][j] + B2[i][j] + ...;
            rhs[3][i][j] = A3[i][j] + B3[i][j] + ...;
        }
    }

    // (2) perform Euler step (or Runge-Kutta, ...)
    for (int d = 0; d < 4; d++) {
        for (int i = 0; i < nr; i++) {
            for (int j = 0; j < ntheta; j++) {
                next[d][i][j] = current[d][i][j] + time_step * rhs[d][i][j];
            }
        }
    }

}

我认为这段代码应该很容易并行化......我把“#pragma omp parellel for”放在(1)和(2)循环前面,我还指定了核心数量(例如4核心) for loop(2)因为有四个变量)但是根本就没有加速。

我发现OpenMP在创建/销毁线程时非常聪明。即它意识到线程很快就会被要求,然后它们才会睡着以节省开销时间。

我认为一个“问题”是我的时间步长是在子程序中编码的(我使用的是RK4而不是Euler),而右手边的计算又是在time_step()调用的另一个子程序中功能。因此,我认为由于这个原因,OpenMP无法看到线程应该保持打开更长时间,因此线程在每个时间步都被创建和销毁。

在时间循环之前放置一个“#pragma omp parallel”以便在最开始创建线程会有帮助吗?然后对右手边(1)和欧拉步骤(2)进行实际的并行化?但是我该怎么做?

我已经找到了很多关于如何并行化嵌套for循环的例子,但是没有一个关注内部循环被分配到单独模块的设置。这会成为并行化的障碍吗?

我现在删除了d循环(通过使索引显式化)并折叠了ij循环(通过仅使用一个变量遍历整个2D数组)。

代码如下:

for (int t = 0; t < max_time_steps; t++) {

    // do some book-keeping
    ...

    // perform time step
    // (1) calculate righthand-side of ODE:
    #pragma omp parallel for
    for (int i = 0; i < nr*ntheta; i++) {
        rhs[0][0][i] = A0[0][i] + B0[0][i] + ...;
        rhs[1][0][i] = A1[0][i] + B1[0][i] + ...;
        rhs[2][0][i] = A2[0][i] + B2[0][i] + ...;
        rhs[3][0][i] = A3[0][i] + B3[0][i] + ...;
    }

    // (2) perform Euler step (or Runge-Kutta, ...)
    #pragma omp parallel for
    for (int i = 0; i < nr*ntheta; i++) {
        next[0][0][i] = current[0][0][i] + time_step * rhs[0][0][i];
        next[1][0][i] = current[1][0][i] + time_step * rhs[1][0][i];
        next[2][0][i] = current[2][0][i] + time_step * rhs[2][0][i];
        next[3][0][i] = current[3][0][i] + time_step * rhs[3][0][i];
    }

}

nr*ntheta的大小为400*40=1600,而我的max_time_steps=1000时间步长为time。尽管如此,并行化并不会导致加速:

没有OpenMP的运行时(命令行上real 0m23.597s user 0m23.496s sys 0m0.076s 的结果):

real   0m23.162s
user   7m47.026s
sys    0m0.905s

使用OpenMP运行时(24核)

double

我不明白这里发生了什么。

我在上面的代码片段中没有显示的一个特点是我的变量实际上不是double,而是两个 static void Main(string[] args) { string CSVPath = @"D:\test.csv"; string outputText = ""; using (var reader = File.OpenText(CSVPath)) { outputText = reader.ReadToEnd(); } var colSplitter = ','; var rowSplitter = new char[] { '\n' }; var rows = (from row in outputText.Split(rowSplitter, StringSplitOptions.RemoveEmptyEntries) let cols = row.Split(colSplitter) from col in cols select new { totalCols = cols.Count(), cols = cols }).ToList(); int[] maxColLengths = new int[rows.Max(o => o.totalCols)]; for (int i = 0; i < rows.Count; i++) { for (int j = 0; j < rows[i].cols.Count(); j++) { int curLength = rows[i].cols[j].Trim().Length; if (curLength > maxColLengths[j]) maxColLengths[j] = curLength; } } Console.WriteLine(string.Join(", ", maxColLengths)); } 的自定义结构,类似于实部和虚部。但我认为这不应该有所作为。

在我单独离开并行化一段时间之后,只想报告一些成功。代码发展了一年,现在我回到了并行化。这一次,我可以说OpenMP可以完成它并减少所需的停机时间。

虽然代码整体发展,但我上面展示的这个特定循环并没有真正改变;只有两件事:a)分辨率更高,因此它覆盖了大约10倍的点数; b)每个循环的计算次数也大约是10倍(甚至更多)。

我唯一解释为什么它现在有效并且在一年多前没有工作的原因是,当我上次尝试并行化代码时,它的计算成本不够高,并且加速被OpenMP开销。单个循环现在需要大约200-300ms,而所需的时间必须是上次单个数字ms。

在比较gcc和英特尔编译器(在矢量化时做的工作非常不同)时,我可以看到这样的效果: a)使用gcc,一个循环在没有OpenMP的情况下需要大约300ms,在两个核心上只需要52%的时间 - &gt;近乎完美的优化。 b)使用icpc,一个循环在没有OpenMP的情况下需要大约160ms,在两个核心上它需要60%的时间 - &gt;良好的优化,但有效率降低约20%。

当使用两个以上内核时,加速不够大,不值得。

0 个答案:

没有答案