OpenMP缓慢减少

时间:2013-12-06 04:55:30

标签: openmp

有两种版本的openmp代码可以减少和不减少。

//减少

#pragma omp parallel for reduction(+:sum)
  for (i=1;i<= num_steps; i++){
      x = (i-0.5)*step;
      sum = sum + 4.0/(1.0+x*x);
  }

//不减少

#pragma omp parallel private(i)
{
  int id = omp_get_thread_num();
  int numthreads = omp_get_num_threads();
  double x;

  double partial_sum = 0;

  for (i=id;i< num_steps; i+=numthreads){
      x = (i+0.5)*step;
      partial_sum += + 4.0/(1.0+x*x);
  }
#pragma omp critical
      sum += partial_sum;
}

我使用8个核心运行代码,缩减版本的总时间加倍。什么原因?感谢。

2 个答案:

答案 0 :(得分:0)

如果你想手动并行化循环以及减少你可以这样做:

#pragma omp parallel private(i)
{
  int id = omp_get_thread_num();
  int numthreads = omp_get_num_threads();
  int start = id*num_steps/numthreads;
  int finish = (id+1)*num_steps/numthreads;
  double x;
  double partial_sum = 0;

  for (i=start; i<finish ; i++){
      x = (i+0.5)*step;
      partial_sum += + 4.0/(1.0+x*x);
  }
  #pragma omp atomic
  sum += partial_sum;
}

但是,我不建议这样做。减少不必使用原子进行,您应该让OpenMP并行化循环。第一种情况是最好的解决方案(但请确保声明x私有)。

编辑根据Hristo的说法,一旦你将x私有化,这两种方法的速度几乎相同。我想解释一下为什么在第二种方法中使用critical而不是原子或允许OpenMP进行减少对于这种情况下的性能几乎没有任何影响。

我可以通过两种方式考虑减少:

  • 使用原子或关键
  • 线性求和部分和
  • 使用树对部分和求和。即如果你有8个核心,你可以得到8个部分和,你可以减少4个部分和,然后是2个部分和,然后是1个。

第一次演员阵容的核心数量呈线性收敛。第二种情况是核心数的对数。因此,我很容易认为第二种情况总是更好。然而,对于仅八个核心,减少完全取决于取部分和。添加8个原子/关键数字而不是3个步骤减少树数将是可以忽略不计的。

如果您有例如1024个核心?然后树只能在10个步骤中减少,线性总和需要1024步。但是对于第二种情况,并且对大数组进行部分求和,常数项可以大得多,例如100万元素可能仍然主导着减少。

所以我怀疑使用原子甚至关键还原对缩短时间的影响可以忽略不计。

答案 1 :(得分:0)

OpenMP中的标量减少通常非常快。在你的案例中观察到的行为是由于两件事以两种不同的方式出错。

在您的第一个代码中,您没有将x设为私有。因此,它在线程之间共享,除了得到不正确的结果之外,执行还会受到数据共享的影响。每当一个线程写入x时,它执行的核心就会向所有其他核心发送一条消息,并使它们使该缓存行的副本无效。当其中任何一个稍后写入x时,必须重新加载整个缓存行,然后所有其他核心中的缓存行都将失效。等等。这会大大减慢速度。

在您的第二个代码中,您使用了OpenMP critical构造。与原子添加相比,这是相对较重的,通常用于在最后实现减少。 x86上的Atomic add使用LOCK指令前缀执行,所有内容都在硬件中实现。另一方面,关键部分是使用互斥锁实现的,需要多个指令并且通常需要忙等待循环。这远远低于原子添加的效率。

最后,由于数据共享条件不佳,您的第一个代码速度会变慢。由于使用了错误的同步原语,您的第二个代码速度变慢。只是在您的特定系统上,后一种效果不如前者那么严重,因此您的第二个示例运行得更快。