OpenMP 导致 heisenbug 段错误
OpenMP causes heisenbug segfault
我正在尝试在OpenMP中并行化一个相当大的for-loop
。大约 20% 的时间它运行良好,但其余时间它会因各种段错误而崩溃,例如;
*** glibc detected *** ./execute: double free or corruption (!prev): <address> ***
*** glibc detected *** ./execute: free(): invalid next size (fast): <address> ***
[2] <PID> segmentation fault ./execute
我的一般代码结构如下;
<declare and initialize shared variables here>
#pragma omp parallel private(list of private variables which are initialized in for loop) shared(much shorter list of shared variables)
{
#pragma omp for
for (index = 0 ; index < end ; index++) {
// Lots of functionality (science!)
// Calls to other deep functions which manipulate private variables
// Finally generated some calculated_values
shared_array1[index] = calculated_value1;
shared_array2[index] = calculated_value2;
shared_array3[index] = calculated_value3;
} // end for
}
// final tidy up
}
就正在发生的事情而言,每个循环迭代都完全独立于彼此的循环迭代,除了它们从共享矩阵中提取数据(但每个循环迭代上的列不同)。在我调用其他函数的地方,它们只是更改私有变量(尽管偶尔会读取共享变量),所以我认为它们是线程安全的,因为它们只会弄乱特定线程本地的东西?唯一写入任何共享变量发生在最后,我们将各种计算值写入一些共享数组,其中数组元素由 for 循环索引索引。此代码是C++的,尽管它调用的代码既是 C 代码又是 C++ 代码。
我一直在试图确定问题的根源,但到目前为止还没有运气。如果我设置 num_theads(1),它运行良好,就像我将for-loop
的内容包含在一个
#pragma omp for
for(index = 0 ; index < end ; index++) {
#pragma omp critical(whole_loop)
{
// loop body
}
}
这大概会产生相同的效果(即任何时候只有一个线程可以通过循环)。
另一方面,如果我将for-loop's
内容包含在两个critical
指令中,例如
#pragma omp for
for(index = 0 ; index < end ; index++) {
#pragma omp critical(whole_loop)
{
// first half of loop body
}
#pragma omp critical(whole_loop2)
{
// second half of loop body
}
}
我得到了不可预测的段错误。同样,如果我将每个函数调用都包含在 critical
指令中,它仍然不起作用。
我认为问题可能与函数调用相关的原因是,当我使用 Valgrind(使用 valgrind --tool=drd --check-stack-var=yes --read-var-info=yes ./execute
)以及 SIGSEGing 进行分析时,我得到了大量加载和存储错误,例如;
Conflicting load by thread 2 at <address> size <number>
at <address> : function which is ultimately called from within my for loop
根据 valgrind 手册,这正是您对比赛条件的期望。当然,这种奇怪的出现/消失问题似乎与竞争条件会给出的非确定性错误一致,但我不明白,如果每个给出明显竞争条件的调用都在关键部分。
可能是错误但我认为不包括的事情;
所有 private() 变量都在
for-loops
内初始化(因为它们是线程本地的)。我已经检查了共享变量具有相同的内存地址,而私有变量具有不同的内存地址。
我不确定同步会有所帮助,但鉴于
critical
指令的进入和退出有隐式的barrier
指令,并且我已经尝试了我的代码版本,其中每个函数调用都包含在(唯一命名的)关键部分中我认为我们可以排除这一点。
任何关于如何最好地进行的想法将不胜感激。我整天都在用头撞这个。显然,我不是在寻找"哦 - 这是问题所在"类型的答案,而是在寻找更多如何在调试/解构方面最好地进行。
可能是问题或可能有帮助的事情;
代码中有一些 std::Vectors 利用 vector.pushback() 函数来添加元素。我记得读到调整矢量大小不是线程安全的,但矢量只是私有变量,因此不会在线程之间共享。我想这样就可以了?
如果我将整个
for-loop
体包含在critical
指令中并慢慢缩小代码块的末尾(因此for-loop
末尾不断增长的区域位于关键部分之外),它会运行良好,直到我公开其中一个函数调用,此时段错误恢复。使用 Valgrind 分析这个二进制文件显示了许多其他函数调用中的竞争条件,而不仅仅是我公开的那个。其中一个函数调用是 GSL 函数,根据 Valgrind,它不会触发任何竞争条件。
我是否需要在被调用的函数中显式定义私有和共享变量?如果是这样,这似乎对 OpenMP 有些限制 - 这是否意味着您需要对调用的任何遗留代码具有 OpenMP 兼容性?
并行化一个大
for-loop
是不是行不通?如果你已经读到这里,谢谢你和Godspeed。
可以回答这个问题,但我想通了,我希望这对某人有所帮助,因为我的系统的行为太奇怪了。
我最终调用的(C)函数之一(my_function
-> intermediate_function
-> lower_function
-> BAD_FUNCTION
)声明了它的许多变量为static
,这意味着它们保留了相同的内存地址,因此本质上充当共享变量。有趣的是,静态覆盖了OpenMP。
我发现这一切是;
使用 Valgrid 确定错误发生的位置,并查看所涉及的特定变量。
将整个
for-loop
定义为关键部分,然后在顶部和底部公开更多代码。和我的老板说话。更多的眼睛总是有帮助的,尤其是因为你被迫用语言表达问题(最终导致我打开罪魁祸首函数并指向声明)
- 为什么PyImport_ImportModule python 3.7.2 中出现段错误?
- 为什么在访问 vtkRenderWindow 的"交互器"变量时会发生段错误?
- 全局向量导致 C++ 程序结束时出现段错误
- 为什么重载运算符<<打印特征类成员会导致段错误?
- 更改条件段错误
- 使用 TTF_RenderText() 加载字体时获取段错误 TTF_OpenFontRW()
- 注册对对象工厂的调用会导致段错误
- pthread_create在构造函数段错误中
- Nanoflann发现邻居提出段错误
- C++ 中的构造函数、继承、堆栈、堆、this-pointer 和段错误
- 具有unique_ptr的 CRTP 会导致段错误
- 增强纤维work_stealing屏障会导致段错误
- 当我返回指向结构的指针向量时出现段错误
- C++为什么我的代码没有爆炸/段错误?
- Pthread段错误,使用指向main中变量的指针
- C++ 模板中的段错误
- 相当于Windows/MSVC上的段错误?
- 为什么我的 LLVM JIT 实现出现段错误?
- 为什么访问我的引用捕获变量会导致我的 lambda 函数出现段错误?
- 为什么自删除的全局 Vulkan 实例仅在添加层时才导致段错误?