我遇到了OpenMP问题。我知道如果你在并行块中增加某些东西,你必须在该表达式之前设置一个原子。但在我的代码中有一部分我不明白。
为什么我必须在这里使用原子?
#pragma omp parallel
{
double distance, magnitude, factor, r;
vector_t direction;
int i, j;
#pragma omp for
for (i = 0; i < n_body - 1; i++)
{
for (j = i + 1; j < n_body; j++)
{
r = SQR (bodies[i].position.x - bodies[j].position.x) + SQR (bodies[i].position.y - bodies[j].position.y);
// avoid numerical instabilities
if (r < EPSILON)
{
// this is not how nature works :-)
r += EPSILON;
}
distance = sqrt (r);
magnitude = (G * bodies[i].mass * bodies[j].mass) / (distance * distance);
factor = magnitude / distance;
direction.x = bodies[j].position.x - bodies[i].position.x;
direction.y = bodies[j].position.y - bodies[i].position.y;
// +force for body i
#pragma omp atomic
bodies[i].force.x += factor * direction.x;
#pragma omp atomic
bodies[i].force.y += factor * direction.y;
// -force for body j
#pragma omp atomic
bodies[j].force.x -= factor * direction.x;
#pragma omp atomic
bodies[j].force.y -= factor * direction.y;
}
}
}
为什么我不必在这里使用它:
#pragma omp parallel
{
vector_t delta_v, delta_p;
int i;
#pragma omp for
for (i = 0; i < n_body; i++)
{
// calculate delta_v
delta_v.x = bodies[i].force.x / bodies[i].mass * dt;
delta_v.y = bodies[i].force.y / bodies[i].mass * dt;
// calculate delta_p
delta_p.x = (bodies[i].velocity.x + delta_v.x / 2.0) * dt;
delta_p.y = (bodies[i].velocity.y + delta_v.y / 2.0) * dt;
// update body velocity and position
bodies[i].velocity.x += delta_v.x;
bodies[i].velocity.y += delta_v.y;
bodies[i].position.x += delta_p.x;
bodies[i].position.y += delta_p.y;
// reset forces
bodies[i].force.x = bodies[i].force.y = 0.0;
if (bounce)
{
// bounce on boundaries (i.e. it's more like billard)
if ((bodies[i].position.x < -body_distance_factor) || (bodies[i].position.x > body_distance_factor))
bodies[i].velocity.x = -bodies[i].velocity.x;
if ((bodies[i].position.y < -body_distance_factor) || (bodies[i].position.y > body_distance_factor))
bodies[i].velocity.y = -bodies[i].velocity.y;
}
}
}
代码现在正在使用,但我根本不明白为什么。 你能救我吗?
亲切的问候 迈克尔
答案 0 :(得分:3)
两个代码示例中的第二个,循环的每个并行迭代在数组的元素[i]上工作,并且从不查看任何相邻元素。因此,循环的每次迭代都不会对循环的任何其他迭代产生影响,并且它们都可以同时执行而无需担心。
然而,在第一个代码示例中,循环的每个并行迭代可以使用索引[j]读取和写入在bodies数组中的任何位置。这意味着两个线程可能正在尝试同时更新相同的内存位置,或者一个线程可能正在写入另一个线程正在读取的位置。为避免竞争条件,您需要确保写入是原子的。
答案 1 :(得分:0)
当多个线程写入同一个内存位置时,您需要使用原子操作符或临界区来防止竞争条件。原子操作符更快但有更多限制(例如,它们仅在POD上运行一些基本操作符)但在您的情况下您可以使用它们。
因此,当线程写入相同的内存位置时,您必须问自己。在第一种情况下,您只将外部循环并行化i
而不是j
上的内部循环,因此您实际上不需要i
上的原子运算符{{1}上的原子运算符}}。
让我们考虑第一种情况的例子。我们假设j
并且有4个线程。
n_body=101
首先,您会看到每个线程都写入一些相同的内存位置。例如,所有线程都使用Thread one i = 0-24, j = 1-100, j range = 100
Thread two i = 25-49, j = 26-100, j range = 75
Thread three i = 50-74, j = 51-100, j range = 50
Thread four i = 75-99, j = 76-100, j range = 25
写入内存位置。这就是为什么你需要j=76-100
的原子运算符。但是,没有线程使用j
写入相同的内存位置。这就是为什么你不需要i
的原子运算符。
在你的第二种情况下,你只有一个循环并且它是并行化的,所以没有线程写入同一个内存位置,因此你不需要原子操作符。
这回答了您的问题,但这里有一些额外的评论可以改善您的代码效果:
还有另一个独立于原子算子的重要观察。您可以看到线程1运行i
100次,而线程4仅运行j
25次。因此,使用调度(静态)(通常是默认调度程序)不能很好地分配负载。对于较大的j
值,情况会变得更糟。
一种解决方案是尝试计划(指导)。我以前没用过这个,但我认为这是正确的解决方案OpenMP: for schedule。 “随着工作的进展,一种特殊的动态调度是指导给每个任务提供越来越小的迭代块。”根据{{3}}引导每个连续的块得到“number_of_iterations_remaining / number_of_threads”。所以从我们给出的例子
n_body
现在请注意,线程分布更均匀。使用静态调度时,第四个线程仅运行了j次25次,现在第四个线程运行了Thread one i = 0-24, j = 1-100, j range = 100
Thread two i = 25-44, j = 26-100, j range = 75
Thread three i = 45-69, j = 46-100, j range = 55
Thread four i = 60-69, j = 61-100, j range = 40
Thread one i = 70-76, j = 71-100
...
40次。
但是让我们更仔细地看看你的算法。在第一种情况下,您正在计算每个物体上的重力。以下是两种方法:
j
但是函数//method1
#pragma omp parallel for
for(int i=0; i<n_body; i++) {
vector_t force = 0;
for(int j=0; j<n_body; j++) {
force += gravity_force(i,j);
}
bodies[i].force = force;
}
= gravity_force(i,j)
所以你不需要计算两次。所以你找到了一个更快的解决方案:
gravity_force(j,i)
第一种方法进行//method2 (see your code and mine below on how to parallelize this)
for(int i=0; i<(n_body-1); i++) {
for(int j=i+1; j<nbody; j++) {
bodies[i].force += gravity_force(i,j);
bodies[j].force += gravity_force(i,j);
}
}
次迭代,第二种方法做(n_bodies-1) nbodies / 2次迭代,首先是n_bodies n_bodies / 2。 但是,第二种情况更难以有效并行化(请参阅下面的代码和我的代码)。它必须使用atmoic操作,负载不平衡。第一种方法进行两次迭代,但负载均匀分布,不需要任何原子操作。你应该测试两种方法,看看哪种方法更快。
要平行第二种方法,你可以做你做的事情:
n_bodies*n_bodies
或者更好的解决方案是使用像这样的私人武力副本:
#pragma omp parallel for schedule(static) // you should try schedule(guided)
for(int i=0; i<(n_body-1); i++) {
for(int j=i+1; j<nbody; j++) {
//#pragma omp atomic //not necessary on i
bodies[i].force += gravity_force(i,j);
#pragma omp atomic //but necessary on j
bodies[j].force += gravity_force(i,j);
}
}