混合 OpenMP 和 xmmintrin SSE Intrinsics - 与非并行版本相比没有得到加速

问题描述

我已经实现了一个带有 xmmintrin.h SSE 指令的 Travelling Salesman 版本,获得了不错的加速。但是现在我也在尝试在它之上实现 OpenMP 线程,并且我看到速度非常缓慢。我在两种情况下都得到了正确答案(即 (i) 仅使用 SSE,或 (ii) 使用 SSE && OpenMP)。

我知道我可能做错了什么,也许比我更有经验的人能发现问题。

我的程序的主循环有以下(简短的)伪代码

int currentNode; 

for(int i = 0; i < numNodes; i++) {
    minimumdistance = DBL_MAX;
    minimumdistanceNode;

    for(int j = 0; j < numNodes; j++) {
        // find distance between 'currentNode' to j-th node
        // ...
        if(jthNodedistance < minimumdistance) {
            minimumdistance = jthNodedistance;
            minimumdistanceNode = jthNode;
        }
    }
    currentNode = minimumdistanceNode;
}

这是我的实现,那仍然是半伪代码因为我仍然刷了一些我认为对性能没有影响的部分,我认为有待发现的问题可以在以下代码片段中找到我的代码。如果您只是省略 #pragma 行,那么以下内容与同一程序的仅 SSE 版本几乎相同,因此我认为我应该只包含 OpenMP 版本:

    int currentNode = 0;

    #pragma omp parallel
    {
        #pragma omp single
        {
            for (int i = 1; i < totalNum; i++) {
            miniumum = DBL_MAX;

            __m128 currentNodeX = _mm_set1_ps(xCoordinates[currentNode]);
            __m128 currentNodeY = _mm_set1_ps(yCoordinates[currentNode]);

            #pragma omp parallel num_threads(omp_get_max_threads())
            {
                float localMinimum = DBL_MAX;
                float localMinimumNode;

                #pragma omp for 
                for (int j = 0; j < loopEnd; j += 4) {
                    // a number of SSE vector calculations to find distance
                    // between the current node and the four nodes we're looking
                    // at in this iteration of the loop:
                    __m128 subXs_0 = _mm_sub_ps(currentNodeX,_mm_load_ps(&xCoordinates[j]));
                    __m128 squareSubXs_0 = _mm_mul_ps(subXs_0,subXs_0);
                    __m128 subYs_0 = _mm_sub_ps(currentNodeY,_mm_load_ps(&yCoordinates[j]));
                    __m128 squareSubYs_0 = _mm_mul_ps(subYs_0,subYs_0);
                    __m128 addXY_0 = _mm_add_ps(squareSubXs_0,squareSubYs_0);

                    float temp[unroll];
                    _mm_store_ps(&temp[0],addXY_0);

                    // skipping stuff here that is about getting the minimum distance and
                    // it's equivalent node,don't think it's massively relevant but
                    // each thread will have its own
                    //  localMinimum
                    //  localMinimumNode
                }


                // updating the global minimumNode in a thread-safe way
                #pragma omp critical (update_minimum)
                {
                    if (localMinimum < minimum) {
                        minimum = localMinimum;
                        minimumNode = localMinimumNode;
                    }
                }
            }

            // within the 'omp single'
            ThisPt = minimumNode;
        }
        }
    }

所以我的逻辑是:

  • omp single 用于顶级 for(int i) for 循环,我只想要 1 个线程专用于此
  • omp parallel num_threads(omp_get_max_threads()) 用于内部 for(int j) for 循环,因为我希望所有内核同时处理这部分代码
  • omp critical 在完整的 for(int j) 循环结束时,因为我想线程安全地更新当前节点。

就运行时间而言,OpenMP 版本的速度通常是仅 SSE 版本的两倍。

在我的代码中是否有任何让您感到特别糟糕的地方,导致 OpenMP 的速度急剧下降?

解决方法

在我的代码中,有什么特别糟糕的事情会突然出现在你身上,那就是 导致 OpenMP 的速度急剧下降?

首先:

omp single 用于顶级 for(int i) for 循环,我只想要 1 专用于此的线程

在您的代码中,您有以下内容:

#pragma omp parallel
{
    #pragma omp single
    {
        for (int i = 1; i < totalNum; i++) 
        {
           #pragma omp parallel num_threads(omp_get_max_threads())
           {
             //....
           }

          // within the 'omp single'
          ThisPt = minimumNode;
       }
    }
}

#pragma omp parallel 创建一组线程,但只有一个线程执行并行任务( #pragma omp single),而其他线程不执行任何操作。您可以简化为:

    for (int i = 1; i < totalNum; i++) 
    {
       #pragma omp parallel num_threads(omp_get_max_threads())
       {
         //....
       }

      ThisPt = minimumNode;
   }

inner only 仍然只由一个线程执行。

第二:

omp 并行 num_threads(omp_get_max_threads()) 用于内部 for(int j) for 循环,因为我希望所有内核都在这部分代码上工作 同时。

问题是这可能会返回逻辑核心的数量而不是物理核心的数量,并且某些代码在使用超线程时可能会表现得更差。因此,我将首先使用不同数量的线程进行测试,从 2、4 等开始,直到您找到代码停止缩放的数字。

在完整的 for(int j) 循环结束时 omp critical ,正如我想要的 线程安全地更新当前节点。

        // updating the global minimumNode in a thread-safe way
        #pragma omp critical (update_minimum)
        {
            if (localMinimum < minimum) {
                minimum = localMinimum;
                minimumNode = localMinimumNode;
            }
        }

这可以通过创建一个数组来代替,其中每个线程将其局部最小值保存在为该线程保留的位置,并且在并行区域之外,初始线程提取 minimum 和 { {1}}:

minimumNode

最后,在完成这些更改后,您尝试检查是否可以用以下内容替换此并行化:

        int total_threads = /..;
        float localMinimum[total_threads] = {DBL_MAX};
        float localMinimumNode[total_threads] = {DBL_MAX};
         
        #pragma omp parallel num_threads(total_threads)
        {
          /... 
        }
        for(int i = 0; i < total_threads; i++){
            if (localMinimum[i] < minimum) {
                minimum = localMinimum[i];
                minimumNode = localMinimumNode[i];
            }
        }