【问题标题】:Efficient Parallel algorithm for array filtering阵列过滤的高效并行算法
【发布时间】:2021-07-01 14:09:31
【问题描述】:

给定一个非常大的数组,我只想选择符合某些条件的元素。我先验地知道将匹配的元素数量。我目前的伪代码是:

filter(list):
  out = list of predetermined size
  i = 0
  for element in list
    if element matches condition
      out[i++] = element
  return out

当尝试并行化之前的算法时,我天真的方法只是使 i 的增量成为原子的(它是 OpenMP 项目的一部分,因此使用了#pragma omp atomic)。然而,即使与串行实现相比,这种实现也会降低性能。有什么更有效的算法来实现这一点?为什么我的速度如此缓慢?

来自 cmets 的信息:

  • 我正在使用 C 和 OpenMP;
  • 目前正在测试一百万个条目;
  • 串行大约需要 7 秒,两个线程并行大约需要 15 秒;
  • 输出数组大小正好是它的一半(问题相当于找到数组的元素小于所述数组的中位数);
  • 我仅使用两个内核对其进行测试。

【问题讨论】:

    标签: c multithreading concurrency parallel-processing openmp


    【解决方案1】:

    但是,与 甚至串行实现。更有效的算法是什么 有没有实现这个?

    瓶颈是原子操作的开销,因此乍一看更有效的算法将是避免使用这种操作的算法。尽管这是可能的,但代码将需要两步方法,例如每个线程会将其找到的元素保存在私有数组中。在并行区域之后,main 线程将收集每个线程找到的所有元素并将它们合并到一个数组中。

    合并到单个数组的第二部分也可以并行或使用 SIMD 指令。

    我创建了一个小代码来模仿你的伪代码:

    #include <time.h>
    #include <omp.h>
    
    int main ()
    {
      int array_size = 1000000;
      int *a = malloc(sizeof(int) * array_size); 
      int *out = malloc(sizeof(int) * array_size/2);
     
      for(int i = 0; i < array_size; i++)
          a[i] = i;   
     
      double start = omp_get_wtime();
      int i = 0;
      #pragma omp parallel for
      for (int n=0 ; n < array_size; ++n ){
          if(a[n] % 2 == 0){
             int tmp;
             #pragma omp atomic capture
             tmp = i++;
             out[tmp] = a[n]; 
          }
      }
      double end = omp_get_wtime();
      printf("%f\n",end-start);
      free(a);
      free(out);
      return 0;
    }
    

    我做了一个 ad hoc 基准测试,结果如下:

    -> Sequential : 0.001597 (s)
    -> 2 Threads  : 0.017891 (s)
    -> 4 Threads  : 0.015254 (s)
    

    因此并行版本要慢得多,这是可以预料的,因为并行执行的工作根本不足以克服 原子 和并行性的开销。

    我也测试过删除 atomic 并留下 race-condition 只是为了检查时间:

    -> Sequential : 0.001597 (s)
    -> 2 Threads  : 0.001283 (s)
    -> 4 Threads  : 0.000720 (s)
    

    因此,如果没有原子,2 和 4 线程的加速分别约为 1.2 和 2.2。所以自然地 atomic 会造成巨大的开销。尽管如此,即使没有任何原子,加速也不是很好。这是您对单独并行代码的最大期望。

    根据您的实际代码以及您的条件对计算的要求如何,即使使用我提到的第二种方法,您也可能无法获得很大的加速。


    来自@Paul G的 cmets 的有用说明:

    即使它不会加快这个特定示例的速度,它也可能 说明这样的问题通常在 使用(并行)独占扫描算法/前缀的并行计算 sum 确定哪个线程从输出的哪个索引开始 大批。然后每个线程都有一个私有输出索引,它正在递增 而且不需要原子。

    这种方法可能比@dreamcrashs 解决方案慢 特殊情况,但更普遍的是每个都有私有数组 线程可能非常棘手(确定它们的大小等),特别是如果 您的输出大于输入(每个输入元素的情况 不给出 0 或 1 个输出元素,但给出 n 个输出元素)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-29
      相关资源
      最近更新 更多