【问题标题】:Maximum single-sell profit - Parallelized version最大单笔销售利润 - 并行版本
【发布时间】:2014-10-09 12:06:00
【问题描述】:

我正在尝试使用 OpenMP API(或 pthreads)来并行化以下代码。它的时间复杂度是 O(n)。 我想知道是否可以将条目数组划分为X 块(X = 线程数)并为每个人并行执行该过程。

这是一个非常经典的算法问题,到目前为止我还没有看到有人尝试实现并行化版本。

重要提示:简单的归约并不能解决这个问题,因为我只从左到右读取数组。所以并行化并不是那么明显......

 #include<stdio.h>

/* The function assumes that there are at least two
   elements in array.
   The function returns a negative value if the array is
   sorted in decreasing order.
   Returns 0 if elements are equal  */
int maxDiff(int arr[], int arr_size)
{
  int max_diff = arr[1] - arr[0];
  int min_element = arr[0];
  int i;
  for(i = 1; i < arr_size; i++)
  {       
    if(arr[i] - min_element > max_diff)                               
      max_diff = arr[i] - min_element;
    if(arr[i] < min_element)
         min_element = arr[i];                     
  }
  return max_diff;
}

【问题讨论】:

  • 我们读取数组的时候要注意方向。我从左到右阅读。
  • 好的 - 所以它比最大差异更微妙一些,您正在寻找一个项目和后续项目之间的最大差异,对吗?
  • @jonathan 没错
  • 更新后的版本似乎做对了,但问题的额外限制将进一步限制缩放。

标签: algorithm parallel-processing pthreads openmp


【解决方案1】:

由于数据依赖性和低计算要求,这不太可能在多核中给您带来很大的加速 - 但是,您可以通过在数组的每个块中计算局部最小值、最大值和局部值来做一些事情区域最好,然后跨块进行比较。由于最后一步,这在 O(N) + O(P2) 时间内运行,进一步限制了可扩展性。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <limits.h>
#include <omp.h>

void tick(struct timeval *t);
double tock(const struct timeval * const t);

unsigned int maxDiff(const int * const arr, const int arr_size)
{
  int max_diff = arr[1] - arr[0];
  int min_element = arr[0];
  int i;
  for(i = 1; i < arr_size; i++)
  {
    if(arr[i] - min_element > max_diff)
      max_diff = arr[i] - min_element;
    if(arr[i] < min_element)
         min_element = arr[i];
  }
  return max_diff;
}

unsigned int ompMaxDiff(const int * const arr, const int arr_size)
{
  int nthreads=omp_get_max_threads();
  int maxes[nthreads];
  int mins [nthreads];
  unsigned int best = 0;

  for (int i=0; i<nthreads; i++) {
    mins [i] = INT_MAX;
    maxes[i] = INT_MIN;
  }

  #pragma omp parallel num_threads(nthreads) default(none) shared(mins, maxes) reduction(max:best) 
  {
      int idx = omp_get_thread_num();
      int min = INT_MAX, max = INT_MIN;

      #pragma omp for schedule(static) 
      for(int i=0; i<arr_size; i++) {
        if (arr[i] < min) min=arr[i];
        if (arr[i] > max) max=arr[i];
        if ((arr[i] - min) > best) best = arr[i] - min;
      }

      mins [idx] = min;
      maxes[idx] = max;
  }

  for (int i=0; i<nthreads-1; i++)
    for (int j=i+1; j<nthreads; j++)
        if ((maxes[j] - mins[i]) > best) best = maxes[j]-mins[i];

  return best;
}

int main(int argc, char **argv) {
    const int nitems=1000000;
    int *data = malloc(nitems*sizeof(int));

    srand(time(NULL));
    for (int i=0; i<nitems; i++)
        data[i] = rand() % 500;    /* numbers between 0 and 500 */


    data[(nitems/2)+1] = -700;
    data[(nitems/2)]   = 700;      /* a trick! shouldn't get 1400, */
                                   /* should get <= 1200 */

    struct timeval start;
    tick(&start);
    unsigned int res = maxDiff(data, nitems);
    double restime = tock(&start);

    printf("Serial: answer = %u, time = %lf\n", res, restime);

    tick(&start);
    res = ompMaxDiff(data, nitems);
    restime = tock(&start);

    printf("OpenMP: answer = %u, time = %lf\n", res, restime);

    free(data);

    return 0;
}

void tick(struct timeval *t) {
    gettimeofday(t, NULL);
}

double tock(const struct timeval * const t) {
    struct timeval now;
    gettimeofday(&now, NULL);
    return (double)(now.tv_sec - t->tv_sec) + ((double)(now.tv_usec - t->tv_usec)/1000000.);
}

在 8 核上运行给出:

$ gcc -fopenmp -O3 -Wall -std=c11 maxdiff.c -o maxdiff
$ ./maxdiff 
Serial: answer = 1199, time = 0.001760
OpenMP: answer = 1199, time = 0.000488

【讨论】:

  • @#$##@$@#!我正要给你固定和正确的解决方案!另外我要说的是,OpenMP 的减少被认为是关联的和可交换的。然而,OPs 函数不是可交换的,但它仍然是关联的(例如矩阵乘法)。在这种情况下,您必须存储每个线程的结果(就像您所做的那样),然后串行操作。对于不可交换的操作,不可能并行化它们。
  • 哎呀-对不起,@Zboson :)。另一方面,可以提供此版本的重大改进。例如,事实证明(谷歌“最大和子序列并行”,这是一个可以转化为的问题)在每个并行区域中进行更多计算,您可以从 O(P2) 到 O(P)。但是对于在这里可能有用的 P 的小范围,我强烈怀疑额外的工作是不值得的。
  • 我不会硬编码线程数,而是询问您的并行团队中有多少线程,然后使用单个语句在并行部分内分配数组。在这里查看 Hristo Iliev 的答案stackoverflow.com/questions/16789242/…
  • @Zboson :是的 - 我真的应该解决这个问题,再加上手动分解 for 循环(不依赖于 schedule(static) 以这种方式分解循环-保证)。
  • 我不确定我是否遵循您关于静态调度的论点。您是否担心它可能不会在增加线程数时分配块?我问了一个关于stackoverflow.com/questions/18746282/…的问题
【解决方案2】:

我不确定 OpenMP 是什么,但这里有一个关联运算符,可以解决适合并行性的问题。

struct intermediate {
    int min_elem;
    int max_elem;
    int max_diff;
};

使用这个函数准备一个单例列表。

struct intermediate singleton(int x) {
    return (struct intermediate){x, x, INT_MIN};
}

使用此函数合并两个相邻的中间体。

struct intermediate combine(struct intermediate a, struct intermediate b) {
    return (struct intermediate){min(a.min_elem, b.min_elem),
                                 max(a.max_elem, b.max_elem),
                                 max(max(a.max_diff, b.max_diff),
                                     b.max_elem - a.min_elem)};
}

一个可能的评估策略可以这样绘制。

        C
       / \
      C   \
     / \   \
    /   \   \
   /     \   \
  C       C   \
 / \     / \   \
S   S   S   S   S
|   |   |   |   |
0   1   2   3   4

这里C 表示组合,S 表示单例。由于 combine 是关联的,因此任何二叉树都可以。这是另一种策略。

        C
       / \
      /   \
     /     \
    /       C
   /       / \
  C       /   C
 / \     /   / \
S   S   S   S   S
|   |   |   |   |
0   1   2   3   4

【讨论】:

  • @Jeb11 您评估类似combine(combine(singleton(a[0]), singleton(a[1])), combine(singleton(a[2]), singleton(a[3]))) 的四元素数组。由于combine 是关联的,因此组合顺序有很大的灵活性。
  • 我只从左到右读取数组,因此您的解决方案不起作用。
  • @Jeb11 这不是 Jonathan 使用的缩减。你试过了吗?
猜你喜欢
  • 2011-10-28
  • 1970-01-01
  • 2011-11-17
  • 2017-11-14
  • 1970-01-01
  • 2016-10-14
  • 2017-06-19
  • 1970-01-01
相关资源
最近更新 更多