如何使用OpenMP正确地并行化for循环?

3
我正在测试C++的OpenMP,因为我的软件将严重依赖于处理器并行化的速度。
当运行以下代码时,我得到了奇怪的结果。
- 并行化加速的速度没有我预期的那么快。 - 当不使用-O标志时,代码运行得更慢了。
我正在使用g++编译器,版本为7.3.0和Ubuntu 18.04操作系统,在i5-8600 CPU上有16 GB RAM。
输出: 输出1(由于我是新成员,暂时无法嵌入)
记录:
.../OpenMPTest$ g++ -O3 -o openmp main.cpp -fopenmp
.../OpenMPTest$ ./openmp
6 processors used.
Linear action took: 2.87415 seconds.
Parallel action took: 0.99954 seconds.

{{链接1: 输出2}}

.../OpenMPTest$ g++ -o openmp main.cpp -fopenmp
.../OpenMPTest$ ./openmp
6 processors used.
Linear action took: 25.7037 seconds.
Parallel action took: 68.0485 seconds.

如您所见,对于6个处理器,我只能获得大约2.9倍的速度提升,除非我省略-O标志,否则程序会运行得更慢,但仍然使用全部6个处理器的100%利用率(使用htop进行测试)。

为什么会这样?另外,我该怎么做才能实现完整的6倍性能提升?

源代码:

#include <iostream>
#include <ctime>
#include <ratio>
#include <chrono>
#include <array>
#include <omp.h>

int main() {

    using namespace std::chrono;

    const int big_number = 1000000000;
    std::array<double, 6> array = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

    // Sequential

    high_resolution_clock::time_point start_linear = high_resolution_clock::now();

    for(int i = 0; i < 6; i++) {
        for(int j = 0; j < big_number; j++) {
            array[i]++;
        }   
    }

    high_resolution_clock::time_point end_linear = high_resolution_clock::now();

    // Parallel 

    high_resolution_clock::time_point start_parallel = high_resolution_clock::now();

    array = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

    #pragma omp parallel
    {
        #pragma omp for
        for(int i = 0; i < 6; i++) {
            for(int j = 0; j < big_number; j++) {
                array[i]++;
            }   
        }
    }

    high_resolution_clock::time_point end_parallel = high_resolution_clock::now();

    // Stats.

    std::cout << omp_get_num_procs() << " processors used." << std::endl << std::endl;

    duration<double> time_span = duration_cast<duration<double>>(end_linear - start_linear);
    std::cout << "Linear action took: " << time_span.count() << " seconds." << std::endl << std::endl;

    time_span = duration_cast<duration<double>>(end_parallel - start_parallel);
    std::cout << "Parallel action took: " << time_span.count() << " seconds." << std::endl << std::endl;

    return EXIT_SUCCESS;
}

1
请指定您使用的处理器和内存。此外,不要测量或优化未经优化的代码。 - Zulan
感谢提供信息丰富、有帮助的答案。我如何确定运行时可用的线程数?由于我正在运行6核CPU,因此我使用了6个线程。 - Sebastian Wieczorek
1个回答

1

看起来你的代码受到了伪共享的影响。

不要让不同的线程访问同一个缓存行。更好的方法是尽量不在线程之间共享变量。

#include <iostream>
#include <ctime>
#include <ratio>
#include <chrono>
#include <array>
#include <omp.h>

int main() {

  using namespace std::chrono;

  const int big_number = 1000000000;
  alignas(64) std::array<double, 6*8> array = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

  // Sequential

  high_resolution_clock::time_point start_linear = high_resolution_clock::now();

  for(int i = 0; i < 6; i++) {
    for(int j = 0; j < big_number; j++) {
      array[i]++;
    }
  }

  high_resolution_clock::time_point end_linear = high_resolution_clock::now();

  // Parallel

  high_resolution_clock::time_point start_parallel = high_resolution_clock::now();

  array = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

      #pragma omp parallel
  {
            #pragma omp for
    for(int i = 0; i < 6; i++) {
      for(int j = 0; j < big_number; j++) {
        array[i*8]++;
      }
    }
  }

  high_resolution_clock::time_point end_parallel = high_resolution_clock::now();

  // Stats.

  std::cout << omp_get_num_procs() << " processors used." << std::endl << std::endl;

  duration<double> time_span = duration_cast<duration<double>>(end_linear - start_linear);
  std::cout << "Linear action took: " << time_span.count() << " seconds." << std::endl << std::endl;

  time_span = duration_cast<duration<double>>(end_parallel - start_parallel);
  std::cout << "Parallel action took: " << time_span.count() << " seconds." << std::endl << std::endl;

  return EXIT_SUCCESS;
}

使用了8个处理器。

线性操作耗时:26.9021秒。

并行操作耗时:6.41319秒。

您可以阅读this


嗨,我编辑了代码并在数组定义之前添加了alignas(64),你可以再试一次。更好的方法是尝试不在线程之间共享变量。我也无法弄清楚为什么不是6x。 - user338371
3
虽然代码看起来确实存在伪共享问题,但我非常怀疑在优化后的代码中实际上并不存在这个问题。 在反汇编代码中可以看到,gcc会将array[i]放入寄存器中。如果每次迭代都受到了伪共享的影响,性能甚至不会比串行代码快。也许未经优化的代码存在这个问题,但这是无关紧要的。 - Zulan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接