【发布时间】:2019-01-14 23:12:12
【问题描述】:
我有一个基于 Barnes-Hut 算法的 N-Body 模拟器,我使用 OpenMP 进行了多线程处理。大多数程序都是通过在几个关键位置简单地添加#pragma omp parallel for 来实现的。这提供了一个健康的加速,当引力体的数量低于几千时,它可以很好地与核心数量相匹配。
因为我的程序使用Barnes-Hut algorithm,它的核心是一个树结构,在二维中这是一个四叉树,在我的例子中是一个八叉树。我在多线程填充树的过程中遇到了麻烦。将此步骤设为单线程会阻止程序充分利用我的处理器。我添加的主体越多,我的 CPU 使用率实际上就会下降,因为只使用一个核心将所有主体添加到八叉树所花费的时间更多。
现在向八叉树添加单个主体的方法如下所示:
void octant::addBody(vec3 newPosition, float newMass) {
// Making room for new bodies by dividing if the node is a leaf
if (isLeaf) {
// Subdividing the octant
divide();
// Moving the body already contained
subdivisionEnclosing(this->position)->addBody(this->position, this->mass);
}
// Adding the body to the appropriate subdivision if the node is divided
if (divided) {
// Adding the new body to the appropriate octant
subdivisionEnclosing(newPosition)->addBody(newPosition, newMass);
return;
}
// If the node doesnt yet contain any bodies at all, add the new one
this->position = newPosition;
this->mass = newMass;
// This node only contains one body, so the center of mass is accurate
isLeaf = true;
calculatedCOM = true;
}
这在串联调用时工作得很好,但当我尝试同时将多个主体添加到同一个根节点时自然会崩溃。此代码不包含任何使八分圆对象线程安全的措施。
理想情况下,我希望能够使用类似这样的方法并行调用 addBody 方法:
#pragma omp parallel for
for (int b = 0; b < bodies.size(); ++b) {
octree->addBody(bodies[b]->getPosition(), bodies[b]->getMass());
}
我已经尝试将#pragma omp critical(name) 添加到更改数据的部分方法和细分节点的#pragma omp single。我没有尝试阻止立即发生段错误。
我还构建了一个批量添加主体的方法。它接收一个身体对象的向量,根据它们适合的细分将它们分类为向量,然后将这些向量传递到它们各自的细分中。每个细分都有自己的线程,并且该过程是递归的。这运行并使用了我所有的核心,但速度明显较慢。我认为将身体放入向量中会增加大量开销。
我对 OpenMP 还很陌生,甚至对线程安全的概念也很陌生。解决这个问题的最佳方法是什么?我似乎无法在网上找到很多线程安全树结构的示例,也没有使用 OpenMP。使用多线程填充树的理想方法是什么?最起码,您认为哪些工具可以让这种事情发挥作用?
编辑: 有谁知道完全线程安全的树结构的任何示例?即使它不在 OpenMP 中,我主要对如何以线程安全的方式添加/生成/填充树感兴趣。
【问题讨论】:
-
也许你应该看看openmp3.0中引入的omp任务。它们是专门为列表、树遍历等引入的。
-
我去看看!如果我最终分批处理尸体,任务可能会很有用。我的主要问题是我不只是遍历树。随着主体的添加,树改变了结构,所以我最终可能会有多个线程试图同时访问和更改一条数据。我是否在正确的道路上使用 omp 关键标志来防止这种情况?
标签: c++ multithreading tree thread-safety openmp