c++ - openmp 嵌套循环处理性能
问题描述
请考虑以下代码:
void process(int N, int K, const vector<int>& data)
{
#pragma omp parallel for
for(int i = 0; i < data.size(); ++i)
{
//perform some processing based on data, N and K
}
}
void solve(int N, int K, const vector<int>& data)
{
for(int i = 0; i < N; ++i)
{
for(int j = 0; j < K; ++j)
{
process(N, K, data);
}
}
}
上面的代码以不同大小的每个参数执行。N 和 K 的范围是 1 - 1000(在大多数情况下)。通常两者都是 1. data.size() 也变化很大,介于 100 到 300 000 之间。
在大多数情况下,上面的代码效果很好。问题是 N 或 K 是否大于 ~100。例如 K 是 300 并且数据不是那么大。例如:1000。在这种情况下,我的程序大部分时间都在等待唤醒 omp 线程。如果我禁用 omp 那么在这种情况下程序会快 2-3 倍。
我的问题是 - 在求解函数中执行循环时,是否有可能以某种方式检测 omp 以保持自旋锁?我已经尝试过 OMP_WAIT_POLICY Active 并且它解决了问题,但是由于其他原因(它只是大型应用程序的一小部分),到目前为止我必须保持被动模式。是否有其他选项可以使线程保持活动一段时间(或任何其他想法如何解决此问题)?
编辑:根据@Gilles,这是我的完整测试程序:
#include <atomic>
#include <iostream>
#include <vector>
#include <chrono>
#include <omp.h>
std::atomic<int> cnt;
void process(int a, int b, std::vector<int>& d)
{
#pragma omp parallel for
for (int i = 0; i < d.size(); ++i)
{
//sample operation
if (d[i] > a + b)
++cnt;
}
}
void solve(int N, int K, std::vector<int>& d)
{
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < K; ++j)
{
process(i, j, d);
}
}
}
void RunTest(int numOfThreads, int N, int K, int arrSize)
{
std::vector<int> s(arrSize);
s[0] = s[10] = 1000;
omp_set_num_threads(numOfThreads);
cnt = 0;
std::chrono::duration<double> minDiff = std::chrono::duration<double>{ 99999999 };
for (int iters = 0; iters < 20; ++iters)
{
auto start = std::chrono::high_resolution_clock::now();
solve(N, K, s);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end - start;
if (diff < minDiff)
minDiff = diff;
}
std::cout << "Time: " << minDiff.count() * 1000 << " ms \t\t" << "Threads: " << numOfThreads << " N: " << N << " K: " << K << std::endl;
}
int main()
{
std::cout << "Large N*K" << std::endl;
RunTest(6, 100, 100, 10000);
RunTest(1, 100, 100, 10000);
std::cout << std::endl;
std::cout << "Small N*K" << std::endl;
RunTest(6, 1, 1, 1000000);
RunTest(1, 1, 1, 1000000);
}
根据主动/被动等待策略的结果(在 MSVC 2019 上测试):
PASSIVE:
Large N*K
Time: 126.358 ms Threads: 6 N: 100 K: 100
Time: 83.0023 ms Threads: 1 N: 100 K: 100
Small N*K
Time: 0.194 ms Threads: 6 N: 1 K: 1
Time: 0.6687 ms Threads: 1 N: 1 K: 1
ACTIVE
Large N*K
Time: 20.8449 ms Threads: 6 N: 100 K: 100
Time: 82.4809 ms Threads: 1 N: 100 K: 100
Small N*K
Time: 0.1404 ms Threads: 6 N: 1 K: 1
Time: 0.6845 ms Threads: 1 N: 1 K: 1
正如您在被动模式下看到的,当 N*K 很大时,时间要长得多。
解决方案
或任何其他想法如何解决这个问题?
在将计算分配给线程时,您希望拥有尽可能大的块和尽可能少的同步。在您的示例中,您应该并行化最外层的循环。在您的示例中,不清楚是否process
修改data
. 它作为非常量传递,但假设它没有被修改,这是我希望表现更好的东西:
void solve(int N, int K, vector<int>& data)
{
#pragma omp parallel for
for(int i = 0; i < N; ++i)
{
for(int j = 0; j < K; ++j)
{
process(N, K, data);
}
} // <-- threads have to wait here until all are finished
}
(简单化)基本原理:产生和收集线程需要时间并引入开销。在您的代码中,您有开销N*K
时间。如果你并行化最外层的循环,你就会有一次开销。
推荐阅读
- java - 带有 SpringBoot Java 的 Azure Cosmos DB - 按页码和大小分页
- javascript - 如何使用 MatDialog 将行数据或数组对象传递给 Angular 中的组件
- javascript - 如何在内联点击事件绑定中使用 document.getElementById?
- javascript - 如何修复第 2 行 C:\xampp\htdocs\TPO\registered.php 中未定义的数组键“令牌”?
- javascript - 无法从 JS 脚本正确地将 TR 添加到 HTML
- reactjs - 如何使用 React 获取具有所有样式的 WordPress api,而不仅仅是纯文本
- public-key-encryption - 如何使用 NaCl/Sodium 原语隐藏发件人的公钥?
- linux - 在 Linux 中识别跨多个文件的匹配行
- reactjs - 蚂蚁V3。表组件。如何计算列值的总和
- python-3.x - 如何在 Quart 中获取变量