使用openMP并行获取最小元素的索引

时间:2015-02-01 01:44:00

标签: c++ openmp

我试着写这段代码

float* theArray; // the array to find the minimum value
int   index, i;
float thisValue, min;

index    = 0;
min = theArray[0];
#pragma omp parallel for reduction(min:min_dist)
for (i=1; i<size; i++) {
    thisValue = theArray[i];
    if (thisValue < min)

    { /* find the min and its array index */

        min = thisValue;

        index    = i;
    }
}
return(index);

然而,这个没有输出正确的答案。似乎min是正常的,但正确的索引已被线程破坏。

我也尝试过在互联网和这里提供的一些方法(使用并行用于外部循环并使用关键进行最终比较)但这会导致速度下降而不是加速。

如何使min值及其索引正确?谢谢!

3 个答案:

答案 0 :(得分:12)

我不知道优雅想要做最小的减少并保存索引。我这样做是通过找到每个线程的局部最小值和索引,然后是关键部分中的全局最小值和索引。

index = 0;
min = theArray[0];
#pragma omp parallel
{
    int index_local = index;
    float min_local = min;  
    #pragma omp for nowait
    for (i = 1; i < size; i++) {        
        if (theArray[i] < min_local) {
            min_local = theArray[i];
            index_local = i;
        }
    }
    #pragma omp critical 
    {
        if (min_local < min) {
            min = min_local;
            index = index_local;
        }
    }
}

使用OpenMP 4.0,可以使用用户定义的缩减。可以像这样定义用户定义的最小减少量

struct Compare { float val; sizt_t index; };    
#pragma omp declare reduction(minimum : struct Compare : omp_out = omp_in.val < omp_out.val ? omp_in : omp_out)

然后可以像这样进行缩减

struct Compare min; 
min.val = theArray[0]; 
min.index = 0;
#pragma omp parallel for reduction(minimum:min)
for(int i = 1; i<size; i++) {
    if(theArray[i]<min.val) { 
        min.val = a[i];
        min.index = i;
    }
}

适用于C和C ++。除简化代码外,用户定义的缩减还有其他优点。有多种算法可用于减少。例如,合并可以在O(number of threads)O(Log(number of threads)中完成。我给出的第一个解决方案在O(number of threads)中执行了此操作,但是使用用户定义的缩减,让OpenMP选择算法。

答案 1 :(得分:2)

因为您不仅要尝试找到最小值(reduction(min:___))而且还要保留索引,因此您需要将检查设为关键。这可以显着减慢循环(如报告的那样)。一般来说,确保有足够的工作,这样您就不会遇到this问题中的开销。另一种方法是让每个线程找到最小值和它的索引并将它们保存到一个唯一变量中,然后让主线程对这些线程进行最终检查,如下面的程序所示。

#include <iostream>
#include <vector>
#include <ctime>
#include <random>
#include <omp.h>

using std::cout;
using std::vector;

void initializeVector(vector<double>& v)
{
    std::mt19937 generator(time(NULL));
    std::uniform_real_distribution<double> dis(0.0, 1.0);
    v.resize(100000000);
    for(int i = 0; i < v.size(); i++)
    {
        v[i] = dis(generator);
    }
}

int main()
{
    vector<double> vec;
    initializeVector(vec);

    float minVal = vec[0];
    int minInd = 0;

    int startTime = clock();

    for(int i = 1; i < vec.size(); i++)
    {
        if(vec[i] < minVal)
        {
            minVal = vec[i];
            minInd = i;
        }

    }

    int elapsedTime1 = clock() - startTime;

    // Change the number of threads accordingly
    vector<float> threadRes(4, std::numeric_limits<float>::max());
    vector<int>   threadInd(4);

    startTime = clock();
#pragma omp parallel for
    for(int i = 0; i < vec.size(); i++)
    {
        {
            if(vec[i] < threadRes[omp_get_thread_num()])
            {
                threadRes[omp_get_thread_num()] = vec[i];
                threadInd[omp_get_thread_num()] = i;
            }
        }

    }

    float minVal2 = threadRes[0];
    int minInd2 = threadInd[0];

    for(int i = 1; i < threadRes.size(); i++)
    {
        if(threadRes[i] < minVal2)
        {
            minVal2 = threadRes[i];
            minInd2 = threadInd[i];
        }
    }

    int elapsedTime2 = clock() - startTime;

    cout << "Min " << minVal << " at " << minInd << " took " << elapsedTime1 << std::endl;
    cout << "Min " << minVal2 << " at " << minInd2 << " took " << elapsedTime2 << std::endl;
}

请注意,通过优化并且在循环中无需执行任何其他操作,串行版本似乎仍然是王道。关闭优化后,OMP占上风。

P.S。你写了reduction(min:min_dist)并继续使用min而不是min_dist

答案 2 :(得分:2)

基本理念

这可以通过创建custom reduction来完成,而不会导致任何打破criticalatomic部分。基本上,定义一个存储索引和值的对象,然后创建一个函数,只用值而不是索引对这些对象中的两个进行排序。

<强>详情

将索引和值存储在一起的对象:

typedef std::pair<unsigned int, float> IndexValuePair;

您可以通过访问first属性和访问second属性的值来访问索引,即

IndexValuePair obj(0, 2.345);
unsigned int ix = obj.first;  // 0
float val = obj.second; // 2.345

定义一个函数来对两个IndexValuePair对象进行排序:

IndexValuePair myMin(IndexValuePair a, IndexValuePair b){
    return a.second < b.second ? a : b;
}

然后,按照OpenMP documentation中的指南构建自定义缩减:

#pragma omp declare reduction \
(minPair:IndexValuePair:omp_out=myMin(omp_out, omp_in)) \
initializer(omp_priv = IndexValuePair(0, 1000))

在这种情况下,我选择将索引初始化为0,将值初始化为1000.该值应初始化为大于您希望排序的最大值的某个数字。

功能示例

最后,将所有这些部分与并行for循环结合起来!

// Compile with g++ -std=c++11 -fopenmp demo.cpp
#include <iostream>
#include <utility>
#include <vector>

typedef std::pair<unsigned int, float> IndexValuePair;

IndexValuePair myMin(IndexValuePair a, IndexValuePair b){
    return a.second < b.second ? a : b;
}

int main(){

    std::vector<float> vals {10, 4, 6, 2, 8, 0, -1, 2, 3, 4, 4, 8};
    unsigned int i;

    IndexValuePair minValueIndex(0, 1000);

    #pragma omp declare reduction \
        (minPair:IndexValuePair:omp_out=myMin(omp_out, omp_in)) \
        initializer(omp_priv = IndexValuePair(0, 1000))

    #pragma omp parallel for reduction(minPair:minValueIndex)
    for(i = 0; i < vals.size(); i++){

        if(vals[i] < minValueIndex.second){
            minValueIndex.first = i;
            minValueIndex.second = vals[i];
        }
    }

    std::cout << "minimum value = " << minValueIndex.second << std::endl;   // Should be -1
    std::cout << "index = " << minValueIndex.first << std::endl;    // Should be 6


    return EXIT_SUCCESS;

}