OpenMP 并行编程指南

并行计算

  对于 CPU 密集型的程序来说,可以考虑使用 OpenMP 加快程序的计算速度。OpenMP 是跨平台的,大部分现代的 C/C++ 编译器都支持 OpenMP。OpenMP 在一定程度上对并行算法进行了抽象,因此它使用起来很方便,程序员可以简单地通过编译器指令#pragma omp去控制程序的行为。
  OpenMP 的语法很简单,它看起来是这样的:

1
#pragma omp <directive> [clause[[,] clause] ...]

  最常见的指令应该算是parallel指令了,紧接在parallel指令后面的那个代码块将会并行地执行:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
int main()
{
#pragma omp parallel
{
std::cout << "Hello, World!" << std::endl;
}
return 0;
}

  程序会启用 N 个线程去执行parallel指令后面的那个代码块 (N 等同于 CPU 的核心数),执行完这个代码块之后,程序又会变回单线程。编译时只需要提供-fopenmp参数,就可以让编译器启用 OpenMP。例如,下面在双核的机器上运行这个程序,将会打印两条消息:

1
2
3
4
$ g++ -std=c++11 -fopenmp -o main main.cpp
$ ./main
Hello, World!
Hello, World!


  OpenMP 还提供了parallel for指令,它的作用就是将 for 循环拆分给 N 个线程去执行,这样每个线程都只需要执行整个 for 循环的其中一部分,所以可以实现并行计算。例如下面的例子,需要计算数组中每个元素的平方,我们可以将它拆分给 N 个线程去执行 (N 等同于 CPU 的核心数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>
#include <cstdint>
using Int = uint64_t;
int main()
{
constexpr Int size = 10 * 1024 * 1024;
std::vector<Int> squares(size, 0);
#pragma omp parallel for
for (Int i = 0; i < size; ++i)
{
squares[i] = i * i;
}
return 0;
}

  上面的代码写法很简单,也很清晰。这就是 OpenMP 的优势,只需要加上简单的编译器指令,就可以轻松实现并行计算。然而,如果我们从 OpenMP 实现的角度来看,上面的代码等同于这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <omp.h>
#include <vector>
#include <cstdint>
using Int = uint64_t;
int main()
{
constexpr Int size = 10 * 1024 * 1024;
std::vector<Int> squares(size, 0);
#pragma omp parallel
{
Int thread_id = omp_get_thread_num(); // 线程 ID, 范围从 0 到 N - 1
Int thread_nums = omp_get_num_threads(); // 线程的数量
Int first = thread_id * size / thread_nums;
Int last = (thread_id + 1) * size / thread_nums;
for (Int i = first; i < last; ++i)
{
squares[i] = i * i;
}
}
return 0;
}

参考资料