并行计算 对于 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 ;
}
参考资料