使用 std::thread 时与 g++/OpenMP 相关的错误?
Bug related to g++/OpenMP when using std::thread?
我已经将我遇到的问题提炼成了最基本的问题。这是第一个示例代码:
#include <vector>
#include <math.h>
#include <thread>
std::vector<double> vec(10000);
void run(void)
{
for(int l = 0; l < 500000; l++) {
#pragma omp parallel for
for(int idx = 0; idx < vec.size(); idx++) {
vec[idx] += cos(idx);
}
}
}
int main(void)
{
#pragma omp parallel
{
}
std::thread threaded_call(&run);
threaded_call.join();
return 0;
}
将其编译为(在 Ubuntu 20.04 上):g++ -fopenmp main.cpp -o main
编辑:版本:g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
运行 在 Ryzen 3700x(8 核,16 线程)上:运行 时间 ~43s,系统监视器中报告的所有 16 个逻辑内核位于~80%.
接下来去掉#pragma omp parallel指令,这样main函数就变成了:
int main(void)
{
std::thread threaded_call(&run);
threaded_call.join();
return 0;
}
现在 运行 时间 ~9s,所有 16 个逻辑内核在系统监视器中报告为 100%。
我还在 Windows 10 上使用 MSVC 编译了这个,cpu 利用率总是 ~100%,无论 #pragma omp parallel 指令是否存在。是的,我完全知道这一行绝对不应该做任何事情,但是对于 g++ 它会导致上述行为;它也只有在线程上调用 运行 函数时才会发生,而不是直接调用。我尝试了各种编译标志(-O 级别),但问题仍然存在。我想下一步是查看汇编代码,但我看不出这只是 g++ 中的错误。任何人都可以对此有所了解吗?将不胜感激。
此外,调用omp_set_num_threads(1);在循环之前的“void 运行(void)”函数中,为了检查单个线程需要多长时间,给出 ~70s 运行 时间只有一个线程处于 100%(如预期)。
此外,可能相关的问题(虽然这可能是我缺乏理解):调用omp_set_num_threads(1);在“int main(void)”函数中(在定义 threaded_call 之前)在使用 g++ 编译时不执行任何操作,即所有 16 个线程仍在 for 循环中执行,而不管伪造的 #pragma omp parallel 指令。当使用 MSVC 编译时,这只会导致一个线程达到预期的 运行 - 根据 omp_set_num_threads 的文档,我认为这应该是正确的行为,但 g++ 并非如此。为什么不呢,这是不是又一个bug?
编辑:我现在理解的最后一个问题 (Overriding OMP_NUM_THREADS from code - for real),但仍未解决原始问题。
感谢 Hristo Iliev 的有用评论,我现在明白了这一点,并想回答我自己的问题,以防它对遇到类似问题的任何人有用。
问题是如果在主程序线程中执行任何 OpenMP 代码,其状态将变为“已污染”- 特别是在“#pragma omp parallel”指令之后,OpenMP 线程仍处于忙碌状态(全部 16 个)并且这会影响所有其他 OpenMP 代码在任何 std::thread 线程中的性能,这些线程会产生自己的 OpenMP 线程组。由于主线程仅在程序完成时才超出范围,因此此性能问题在整个程序执行过程中仍然存在。因此,如果将 OpenMP 与 std::thread 一起使用,请确保主程序线程中绝对不存在 OpenMP 代码。
为了证明这一点,请考虑以下修改后的示例代码:
#include <vector>
#include <math.h>
#include <thread>
std::vector<double> vec(10000);
void run(void)
{
for(int l = 0; l < 500000; l++) {
#pragma omp parallel for
for(int idx = 0; idx < vec.size(); idx++) {
vec[idx] += cos(idx);
}
}
}
void state(void)
{
#pragma omp parallel
{
}
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
}
int main(void)
{
std::thread state_thread(&state);
state_thread.detach();
std::thread threaded_call(&run);
threaded_call.join();
return 0;
}
此代码在前 5 秒以 80% CPU 的利用率运行,然后在程序运行期间以 100% CPU 的利用率运行。这是因为在第一个 std::thread 中生成了一组 16 个 OpenMP 线程并保持忙碌状态,从而影响了第二个 std::thread 中 OpenMP 代码的性能。一旦第一个 std::thread 终止,第二个 std::thread 的性能不再受到影响,因为第二个 16 个 OpenMP 线程团队现在不必与第一个竞争 CPU 访问权限.当有问题的代码在主线程中时,问题一直存在到程序结束。
我已经将我遇到的问题提炼成了最基本的问题。这是第一个示例代码:
#include <vector>
#include <math.h>
#include <thread>
std::vector<double> vec(10000);
void run(void)
{
for(int l = 0; l < 500000; l++) {
#pragma omp parallel for
for(int idx = 0; idx < vec.size(); idx++) {
vec[idx] += cos(idx);
}
}
}
int main(void)
{
#pragma omp parallel
{
}
std::thread threaded_call(&run);
threaded_call.join();
return 0;
}
将其编译为(在 Ubuntu 20.04 上):g++ -fopenmp main.cpp -o main
编辑:版本:g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
运行 在 Ryzen 3700x(8 核,16 线程)上:运行 时间 ~43s,系统监视器中报告的所有 16 个逻辑内核位于~80%.
接下来去掉#pragma omp parallel指令,这样main函数就变成了:
int main(void)
{
std::thread threaded_call(&run);
threaded_call.join();
return 0;
}
现在 运行 时间 ~9s,所有 16 个逻辑内核在系统监视器中报告为 100%。
我还在 Windows 10 上使用 MSVC 编译了这个,cpu 利用率总是 ~100%,无论 #pragma omp parallel 指令是否存在。是的,我完全知道这一行绝对不应该做任何事情,但是对于 g++ 它会导致上述行为;它也只有在线程上调用 运行 函数时才会发生,而不是直接调用。我尝试了各种编译标志(-O 级别),但问题仍然存在。我想下一步是查看汇编代码,但我看不出这只是 g++ 中的错误。任何人都可以对此有所了解吗?将不胜感激。
此外,调用omp_set_num_threads(1);在循环之前的“void 运行(void)”函数中,为了检查单个线程需要多长时间,给出 ~70s 运行 时间只有一个线程处于 100%(如预期)。
此外,可能相关的问题(虽然这可能是我缺乏理解):调用omp_set_num_threads(1);在“int main(void)”函数中(在定义 threaded_call 之前)在使用 g++ 编译时不执行任何操作,即所有 16 个线程仍在 for 循环中执行,而不管伪造的 #pragma omp parallel 指令。当使用 MSVC 编译时,这只会导致一个线程达到预期的 运行 - 根据 omp_set_num_threads 的文档,我认为这应该是正确的行为,但 g++ 并非如此。为什么不呢,这是不是又一个bug?
编辑:我现在理解的最后一个问题 (Overriding OMP_NUM_THREADS from code - for real),但仍未解决原始问题。
感谢 Hristo Iliev 的有用评论,我现在明白了这一点,并想回答我自己的问题,以防它对遇到类似问题的任何人有用。
问题是如果在主程序线程中执行任何 OpenMP 代码,其状态将变为“已污染”- 特别是在“#pragma omp parallel”指令之后,OpenMP 线程仍处于忙碌状态(全部 16 个)并且这会影响所有其他 OpenMP 代码在任何 std::thread 线程中的性能,这些线程会产生自己的 OpenMP 线程组。由于主线程仅在程序完成时才超出范围,因此此性能问题在整个程序执行过程中仍然存在。因此,如果将 OpenMP 与 std::thread 一起使用,请确保主程序线程中绝对不存在 OpenMP 代码。
为了证明这一点,请考虑以下修改后的示例代码:
#include <vector>
#include <math.h>
#include <thread>
std::vector<double> vec(10000);
void run(void)
{
for(int l = 0; l < 500000; l++) {
#pragma omp parallel for
for(int idx = 0; idx < vec.size(); idx++) {
vec[idx] += cos(idx);
}
}
}
void state(void)
{
#pragma omp parallel
{
}
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
}
int main(void)
{
std::thread state_thread(&state);
state_thread.detach();
std::thread threaded_call(&run);
threaded_call.join();
return 0;
}
此代码在前 5 秒以 80% CPU 的利用率运行,然后在程序运行期间以 100% CPU 的利用率运行。这是因为在第一个 std::thread 中生成了一组 16 个 OpenMP 线程并保持忙碌状态,从而影响了第二个 std::thread 中 OpenMP 代码的性能。一旦第一个 std::thread 终止,第二个 std::thread 的性能不再受到影响,因为第二个 16 个 OpenMP 线程团队现在不必与第一个竞争 CPU 访问权限.当有问题的代码在主线程中时,问题一直存在到程序结束。