<aside> <img src="/icons/condense_yellow.svg" alt="/icons/condense_yellow.svg" width="40px" /> C++ | GPU | 线程 | 并发 | 几何分解 | 并行结构 | 分布式内存 | 堆栈 | 曼德尔布罗分形集 | OpenMP | 扩散限制聚集 | 模拟 | 数据消息加密解密 | 工作负载 | 排序
</aside>
OpenMP 是一个在共享内存多处理环境中进行并行编程的库。 它是一种应用程序编程接口 (API),允许开发人员编写可以利用单台计算机内多个内核和处理器的计算能力的程序。 该库得到编译器(gcc、clang、msvc)的良好支持,并且是可移植的,可以在 Linux、macOS 和 Windows 上运行。
最重要的是,它的 API 足够简单和直观,您可以专注于程序的并行设计,而不是陷入并行编程实现的细节中。 使用 OpenMP,您可以简单地向代码中添加一些编译器指令,指示代码的哪些部分应该并行执行,而库会处理其余的事情。 这显着减少了编写并行程序所需的工作量和时间,使其成为希望在不牺牲代码可读性和可维护性的情况下提高应用程序性能的开发人员的必备工具。
那么,让我们从一个简单的例子开始。 最简单的示例是一个简单的 for 循环,它单独执行一个表达式。 考虑以下估计 π 值的函数:
double estimate_pi_seq(uint64_t num_steps) {
auto delta = 1.0 / static_cast<double>(num_steps);
double pi = 0.0;
for (uint64_t step = 0; step < num_steps; ++step) {
auto x = delta * (static_cast<double>(step) + 0.5);
pi += 4.0 / (1 + x * x);
}
return pi * delta;
}
该函数通过函数 4 /\left(1+x^{\wedge} 2\right) 从 0 到 1 的数值积分来估计 \pi 的值。使用大 num_steps 评估函数可以很好地逼近 \pi。
现在,让我们使用 OpenMP 并行化该函数。鉴于循环内的每个计算都是独立的,我们可以轻松地使用parallel-for 并行化代码。在 OpenMP 中,可以写为
double estimate_pi_par(uint64_t num_steps) {
auto delta = 1.0 / static_cast<double>(num_steps);
double pi = 0.0;
#pragma omp parallel for default(none) shared(num_steps, delta) reduction(+:pi)
for (uint64_t step = 0; step < num_steps; ++step) {
auto x = delta * (static_cast<double>(step) + 0.5);
pi += 4.0 / (1 + x * x);
}
return pi * delta;
}
也许更容易显示两者之间的差异:
--- seq
+++ par
@@ -1,6 +1,7 @@
-double estimate_pi_seq(uint64_t num_steps) {
+double estimate_pi_par(uint64_t num_steps) {
auto delta = 1.0 / static_cast<double>(num_steps);
double pi = 0.0;
+#pragma omp parallel for default(none) shared(num_steps, delta) reduction(+:pi)
for (uint64_t step = 0; step < num_steps; ++step) {
auto x = delta * (static_cast<double>(step) + 0.5);
pi += 4.0 / (1 + x * x);
本质上,我们用一行并行化代码。 就是这样。 此行告诉编译器使用 OpenMP 并并行化 for 循环。 这里,shared()显式指定哪些变量是跨线程共享的,reduction()指定要从部分结果中减少哪个变量。 所有其他未指定的变量都假定为私有的,即只能由其自己的线程访问。 通过这个单行代码,编译器将为我们完成所有艰苦的工作并生成高效的并行实现。
我们甚至不需要担心低级实现来解决 \pi 变量的竞争条件 - 我们明确地将其指定为归约变量,并且 OpenMP 根据我们的规范来处理它。 请注意,OpenMP 不是一根魔杖。 它不会找出哪些变量造成竞争条件。 程序员有责任了解哪些变量会引起竞争条件,并需要明确要求 OpenMP 来处理它。
#include <iostream>
#include <omp.h>
#include <string>
using namespace std::string_literals;
double estimate_pi_seq(uint64_t num_steps) {
auto delta = 1.0 / static_cast<double>(num_steps);
double pi = 0.0;
for (uint64_t step = 0; step < num_steps; ++step) {
auto x = delta * (static_cast<double>(step) + 0.5);
pi += 4.0 / (1 + x * x);
}
return pi * delta;
}
double estimate_pi_par(uint64_t num_steps) {
auto delta = 1.0 / static_cast<double>(num_steps);
double pi = 0.0;
#pragma omp parallel for default(none) shared(num_steps, delta) reduction(+:pi)
for (uint64_t step = 0; step < num_steps; ++step) {
auto x = delta * (static_cast<double>(step) + 0.5);
pi += 4.0 / (1 + x * x);
}
return pi * delta;
}
int main(int argc, const char **argv) {
uint64_t num_steps = std::stoull(argv[1]);
auto calc_pi = [argv](uint64_t num_steps) {
return "seq"s == argv[2] ? estimate_pi_seq(num_steps) : estimate_pi_par(num_steps);
};
auto pi = calc_pi(num_steps);
std::cout << pi << "\\n";
}