这是困扰我的最小例子
#include <iostream>
#include <memory>
#include"omp.h"
class A{
public:
A(){std::cout<<this<<std::endl;}
};
int main(){
#pragma omp parallel for
for(unsigned int i=0;i<4;i++){
std::shared_ptr<A> sim(std::make_shared<A>());
}
for(unsigned int i=0;i<4;i++){
std::shared_ptr<A> sim(std::make_shared<A>());
}
}
如果我运行该代码几次,我可能会得到这样的结果:
0xea3308
0xea32d8
0xea3338
0x7f39f80008c8
0xea3338
0xea3338
0xea3338
0xea3338
我意识到最后4个输出的数量始终相同 人物(8)。但由于某种原因,它发生(并非总是)一个或多个 四个第一个输出包含更多(14)个字符。它看起来像是使用 openmp改变了&#34; nature&#34;指针(这是我天真的理解)。 但这种行为是正常的吗?我应该期待一些奇怪的行为吗?
here是一个实时测试,在稍微复杂的代码版本中显示相同的问题
答案 0 :(得分:3)
这种行为是完全合理的,让我们看看发生了什么。
在每次迭代中,您都会在堆上创建一个A
,而其中一个正在被销毁。这些操作的顺序如下:
由于正在堆上创建A
,因此它们将通过内存分配器。当内存分配器获得新内存请求时,如步骤3,它将(在许多情况下)首先查看最近释放的内存。它看到最后一个操作是一个没有完全正确大小的内存(步骤2),因此将再次占用该块内存。此过程将在每次迭代中重复。因此,串行循环将(通常但不一定)一遍又一遍地为您提供相同的地址。
现在让我们考虑并行循环。由于没有同步,因此不确定存储器分配和解除分配的顺序。因此,它们可以以您能想象的任何方式交错。因此,内存分配器通常不能使用与上次相同的技巧来始终分配同一块内存。一个示例排序可能是例如所有四个A
在它们全部被销毁之前构建 - 如下所示:
因此,内存分配器必须提供 4个全新的内存,然后才能恢复并开始回收。
基于堆栈的版本的行为稍微更具确定性,但可能依赖于编译器优化。对于串行版本,每次创建/销毁对象时,都会调整堆栈指针。由于两者之间没有任何事情发生,因此它将继续在同一位置创建。
对于并行版本,每个线程在共享内存系统中都有自己的堆栈。因此,每个线程都会在不同的内存位置创建它的对象,并且没有&#34;回收&#34;是可能的。
您所看到的行为绝不是奇怪的,或者保证这一点。它取决于您拥有的物理内核数量,运行的线程数,您使用的迭代次数 - 通常是运行时条件。
底线:一切都很好,你不应该读太多。
答案 1 :(得分:1)
我认为这取决于您的环境,这不是一种吝啬,不应被视为奇怪的行为。 使用MS VS 2015预览版,您的代码为我提供了以下内容(启用了OMP):
0082C3DC
0082C41C
0082C49C
0082C45C
0082C41C
0082C41C
0082C41C
0082C41C