3

我刚刚完成了计算机图形学课程,我们必须在其中编写光线追踪器。尽管所有结果都是正确的,但我对 OpenMP 的使用感到困惑(顺便说一句,这不是课程的一部分)。我有这个循环(C++):

#pragma omp parallel for private(L, ray)
//  for (x = x_from; x < x_till; x++) {
//  printf("Col: %5d\n", x);
//  for (y = y_from; y < y_till; y++) {
  for (int xy = 0; xy < xy_range; xy++) {
    int x = x_from + (xy % x_width);
    int y = y_from + (xy / x_width);
        ray = cam->get_ray_at(x, y);
        L = trace_ray(ray, 0, cam->inter);
    #pragma omp critical
    cam->set_pixel(x, y, L);
  }
//  }
}

我尝试了很多配置。但最终让我最困惑的是,上面的版本,结合了单个 for,是所有版本中效率最低的(150 秒 vs 120 秒,单独的 x 和 y for。“关键”并没有明显改变时间。

更多:虽然我希望单个 for 循环能够并行化每个单独的迭代,但事实并非如此。使用这种方法,25 个循环作为 8 - 8 - 8 - 1 组(8 个核心)执行。事实上,单独的 y 循环(在清单中已注释掉)似乎更有效地分配了负载。删除“parallel for”中的“for”确实略有改善(148 vs 150s ;)

此外,我尝试了本地与全局定义(使用必要的私有编译指示)。我试图在循环中声明 L 和 ray 。一切都无济于事...

我会很感激建议或指示...

以下是一些更精确的数据:

Single loop             Yes                     No                      No                      Yes    
'Critical"              No                      No                      Yes                     Yes
                ----------------------  ----------------------  ----------------------  ----------------------
                User    CPU     Mean    User    CPU     Mean    User    CPU     Mean    User    CPU     Mean
Scene 5         37.9    158.9   3.66    26.5    185.5   7.00    27.0    187.7   6.95    38.7    161.8   4.18
Scene 6         18.8    110     5.85    17.7    112     6.32    18.1    113.8   5.29    19.4    112.2   5.78
Scene 7         149     658.8   4.42    114     679.9   5.96    114     653.8   5.73    149     659.8   4.43
Plane           112.0   497.3   4.44    105     520.5   4.95    103.8   525     5.06    113.5   504.8   4.45     
5-balls         126     760.2   6.03    162.3   697.5   4.36    170.3   725.3   4.23    127.3   766.5   6.02

'Mean' 是 CPU/User,即平均核心占用。请注意,在某些情况下,均值仅为 4.xx。

解决方案和结果:

Single loop             Yes                     No
                ----------------------  ----------------------
                User    CPU     Mean    User    CPU     Mean
Scene 5         23.9    190.1   7.95    24.4    190.7   7.82
Scene 6         14.3    114.2   7.98    14.5    114.9   7.92
Scene 7         85.5    675.9   7.91    106.9   698.8   6.54
Plane           72.7    579.1   7.97    72.6    578.4   7.97
5-balls         104.8   823.3   7.86    103.9   825.1   7.94

这个出色的结果是通过将 schedule(dynamic, 1) 添加到 #pragma omp parallel for 行来获得的,如下所示:

#pragma omp parallel for schedule(dynamic, 1)

它关注内核的运行时负载分布(而不是编译时)。

还有一点需要注意,', 1' 参数是为了限制块的大小。可以省略,在这种情况下 openmp 使用默认值。也许添加 1 会使负载分布过于细化,但在这种情况下我找不到任何性能差异。我猜光线追踪任务太慢并且隐藏了任何管理开销。

4

1 回答 1

3

我写了一个 Whitted 风格的光线追踪器,它在 OpenCL 中对完整的光线树(反射和折射)进行操作。我还没有使用 OpenMP 完成它,但这是我的下一个目标。如果你想学习 OpenMP,我会先从一些更简单的任务开始。但让我发表一些评论。

你的时间安排如何?你写了“删除'parallel for'中的'for'确实略有改善”。这是没有意义的。删除 for 将在每个线程上运行相同的代码,而不是将踏板分布到不同的迭代中(做一些 hello world 测试来证明这一点)。它应该更慢而不是更快。这让我想知道你是如何计时的。我添加了一些代码来展示如何进行计时。

你不应该使用critical. 如果每次迭代都写入不同的像素,那么它应该是没有必要的。根据场景的复杂性,critical它可能会慢得多。

最后,为了获得最佳性能,您还需要使用 SSE/AVX 并同时对多个像素进行操作。这可以通过所谓的基于数据包的光线追踪来完成。有关此http://graphics.stanford.edu/~boulos/papers/cook_gi07.pdf的详细讨论,请参阅以下链接

编辑:由于每个像素可能需要不同的时间,因此您希望使用 schedule(dynamic) 而不是 schedule(static),这通常是(但不一定)默认设置。查看代码。

Ingo Wald 博士论文: http ://www.sci.utah.edu/~wald/PhD/

double dtime = omp_get_wtime();
#pragma omp parallel
{
    Ray ray;
    Color L;
    #pragma omp for schedule(dynamic)
    for (int xy = 0; xy < xy_range; xy++) {
        int x = x_from + (xy % x_width);
        int y = y_from + (xy / x_width);
        ray = cam->get_ray_at(x, y);
        L = trace_ray(ray, 0, cam->inter);
        cam->set_pixel(x, y, L);
     }
}
dtime = omp_get_wtime() - dtime;
printf("time %f\n", dtime);
于 2013-05-06T17:26:05.943 回答