如何最佳地并行化嵌套循环?

时间:2016-11-19 10:06:24

标签: c++ parallel-processing openmp

我正在编写一个应该在串行和并行版本中运行的程序。一旦我真正做到它应该做的事情,我就开始尝试将它与OpenMP并行化(强制性)。

问题是我无法找到何时使用#pragma的文档或参考资料。所以我在猜测和测试时尽我所能。但是嵌套循环的测试并不顺利。

如何并行化一系列嵌套循环:

for(int i = 0; i < 3; ++i){
    for(int j = 0; j < HEIGHT; ++j){
        for(int k = 0; k < WIDTH; ++k){
            switch(i){
                case 0:
                        matrix[j][k].a = matrix[j][k] * someValue1;
                        break;
                case 1:
                        matrix[j][k].b = matrix[j][k] * someValue2;
                        break;   
                case 2:
                        matrix[j][k].c = matrix[j][k] * someValue3;                
                        break;
            }
        }
    }
}
  • 在我必须运行的测试中,HEIGHT和WIDTH的大小通常相同。一些测试示例是32x32和4096x4096。
  • matrix是一个包含属性a,b和c
  • 的自定义结构数组
  • someValue是双

我知道OpenMP并不总是适用于嵌套循环,但欢迎任何帮助。

[UPDATE]:

到目前为止,我已尝试展开循环。它提升了性能,但我在这里添加了不必要的开销吗?我在重复使用线程吗?我尝试获取每个中使用的线程的ID,但没有做到正确。

#pragma omp parallel
        {
#pragma omp for collapse(2)
            for (int j = 0; j < HEIGHT; ++j) {
                for (int k = 0; k < WIDTH; ++k) {
                    //my previous code here
                }
            }
#pragma omp for collapse(2)
            for (int j = 0; j < HEIGHT; ++j) {
                for (int k = 0; k < WIDTH; ++k) {
                    //my previous code here
                }
            }
#pragma omp for collapse(2)
            for (int j = 0; j < HEIGHT; ++j) {
                for (int k = 0; k < WIDTH; ++k) {
                    //my previous code here
                }
            }
        }

[更新2]

除了展开循环外,我还尝试了并行化外循环(性能提升比展开最差)并折叠两个内循环(与展开相比,性能提升或多或少)。这是我得到的时间。

  • 序列号:~130毫秒
  • 循环展开:~49 ms
  • 折叠两个最里面的循环:~55 ms
  • 并行最外层循环:~83 ms

您认为最安全的选择是什么?我的意思是,对于大多数系统而言,这应该是最好的,而不仅仅是我的电脑?

3 个答案:

答案 0 :(得分:1)

您可能希望并行化此示例for simd,以便编译器可以向量化collapse循环,因为您只在表达式{{1}中使用jk因为矩阵的任何其他元素都没有依赖关系。如果没有任何内容修改matrix[j][k]等,则它们应为somevalue1。为你的循环计时,确保那些确实能提高你的速度。

答案 1 :(得分:1)

OpenMP的问题在于它非常高级,这意味着您无法访问低级功能,例如生成线程,然后重新使用它。因此,让我明确说明你能做什么以及你能做什么:

假设您不需要任何互斥锁来防范race conditions,可以选择以下选项:

  1. 您将最外层的循环并行化,并且将使用3个线程,这是您将拥有的最和平的解决方案

  2. 您将第一个内部循环并行化,然后如果为每个WIDTH元素生成新线程的开销小得多,那么您将只有 需要执行最内循环。

  3. 并行化最内层的循环,但这是世界上最糟糕的解决方案,因为你将3 * HEIGHT次重新生成线程。永远不要那样做!

  4. 不使用OpenMP,并使用低级别的东西,例如std::thread,您可以在其中创建自己的线程池,并推送您想要在队列中执行的所有操作。

  5. 希望这有助于把事情放在眼里。

答案 2 :(得分:1)

这是另一个选项,它认识到当只有3个循环时,分配最外层循环的迭代可能会导致负载平衡非常差,

i=0
#pragma omp parallel for
for(int j = 0; j < HEIGHT; ++j){
    for(int k = 0; k < WIDTH; ++k){
    ...
}

i=1
#pragma omp parallel for
for(int j = 0; j < HEIGHT; ++j){
    for(int k = 0; k < WIDTH; ++k){
    ...
}

i=2
#pragma omp parallel for
for(int j = 0; j < HEIGHT; ++j){
    for(int k = 0; k < WIDTH; ++k){
    ...
}

警告 - 自己检查一下语法,这只不过是手动循环展开的草图。

尝试将其合并并折叠jk循环。

哦,不要抱怨代码重复,你告诉我们你的性能提升部分得分。