_kmp OpenMP中未知调用的巨大开销和旋转时间?

时间:2017-04-29 18:54:08

标签: c++ parallel-processing openmp vtune spinning

我使用英特尔VTune分析我的并行应用程序。

如您所见,应用程序开头有一个巨大的旋转时间(表示为左侧的橙色部分):

enter image description here

超过28%的应用程序持续时间(约为0.14秒)!

正如您所看到的,这些功能是_clonestart_thread_kmp_launch_thread_kmp_fork_barrier,它们看起来像OpenMP内部或系统调用,但它们是没有说明调用这些功能的地方。

此外,如果我们在本节的开头放大,我们可以注意到一个区域实例化,由所选区域表示:

enter image description here

但是,我从不致电initInterTab2d,我不知道我是否使用了某些工作室(尤其是OpenCV)。

深入挖掘并运行高级热点分析我发现了更多关于第一次未知功能的信息:

enter image description here

并对“功能/调用堆栈”选项卡进行说明:

enter image description here

但同样,我不能真正理解为什么这些功能,为什么它们需要这么长时间以及为什么只有主线程在它们中工作,而其他人则处于"屏障"状态。

如果您有兴趣,this是部分代码的链接。

请注意,我只有一个#pragma omp parallel区域,这是此图片的选定部分(位于右侧):

enter image description here

代码结构如下:

  1. 计算一些连续的,不可并行化的东西。特别是,计算一系列模糊,由gaussianBlur表示(包含在代码的末尾)。 cv::GaussianBlur是一个利用IPP的OpenCV函数。
  2. 启动并行区域,其中使用3 parallel for
  3. 第一个调用hessianResponse
  4. 单个线程将结果添加到共享向量。
  5. 第二个并行区域localfindAffineShapeArgs生成下一个并行区域使用的数据。由于负载不平衡,这两个区域无法合并。
  6. 第三个区域以平衡的方式生成最终结果。
  7. 注意:根据VTune的锁定分析,criticalbarrier部分不是旋转的原因。
  8. 这是代码的主要功能:

    void HessianDetector::detectPyramidKeypoints(const Mat &image, cv::Mat &descriptors, const AffineShapeParams ap, const SIFTDescriptorParams sp)
    {
       float curSigma = 0.5f;
       float pixelDistance = 1.0f;
       cv::Mat octaveLayer;
    
       // prepare first octave input image
       if (par.initialSigma > curSigma)
       {
          float sigma = sqrt(par.initialSigma * par.initialSigma - curSigma * curSigma);
          octaveLayer = gaussianBlur(image, sigma);
       }
    
       // while there is sufficient size of image
       int minSize = 2 * par.border + 2;
       int rowsCounter = image.rows;
       int colsCounter = image.cols;
       float sigmaStep = pow(2.0f, 1.0f / (float) par.numberOfScales);
       int levels = 0;
       while (rowsCounter > minSize && colsCounter > minSize){
           rowsCounter/=2; colsCounter/=2;
           levels++;
       }
       int scaleCycles = par.numberOfScales+2;
    
       //-------------------Shared Vectors-------------------
        std::vector<Mat> blurs (scaleCycles*levels+1, Mat());
        std::vector<Mat> hessResps (levels*scaleCycles+2); //+2 because high needs an extra one
        std::vector<Wrapper> localWrappers;
        std::vector<FindAffineShapeArgs> findAffineShapeArgs;
        localWrappers.reserve(levels*(scaleCycles-2));
        vector<float> pixelDistances;
        pixelDistances.reserve(levels);
    
        for(int i=0; i<levels; i++){
           pixelDistances.push_back(pixelDistance);
           pixelDistance*=2;
        }
    
       //compute blurs at all layers (not parallelizable)
       for(int i=0; i<levels; i++){
           blurs[i*scaleCycles+1] = octaveLayer.clone();
           for (int j = 1; j < scaleCycles; j++){
               float sigma = par.sigmas[j]* sqrt(sigmaStep * sigmaStep - 1.0f);
               blurs[j+1+i*scaleCycles] = gaussianBlur(blurs[j+i*scaleCycles], sigma);
               if(j == par.numberOfScales)
                   octaveLayer = halfImage(blurs[j+1+i*scaleCycles]);
           }
       }
    
       #pragma omp parallel
       {
    
       //compute all the hessianResponses
        #pragma omp for collapse(2) schedule(dynamic)
        for(int i=0; i<levels; i++)
            for (int j = 1; j <= scaleCycles; j++)
            {
                int scaleCyclesLevel = scaleCycles * i;
                float curSigma = par.sigmas[j];
                hessResps[j+scaleCyclesLevel] = hessianResponse(blurs[j+scaleCyclesLevel], curSigma*curSigma);
            }
    
        //we need to allocate here localWrappers to keep alive the reference for FindAffineShapeArgs
        #pragma omp single
        {
            for(int i=0; i<levels; i++)
                for (int j = 2; j < scaleCycles; j++){
                    int scaleCyclesLevel = scaleCycles * i;
                    localWrappers.push_back(Wrapper(sp, ap, hessResps[j+scaleCyclesLevel-1], hessResps[j+scaleCyclesLevel], hessResps[j+scaleCyclesLevel+1],
                                        blurs[j+scaleCyclesLevel-1], blurs[j+scaleCyclesLevel]));
                }
        }
    
        std::vector<FindAffineShapeArgs> localfindAffineShapeArgs;
        #pragma omp for collapse(2) schedule(dynamic) nowait
        for(int i=0; i<levels; i++)
            for (int j = 2; j < scaleCycles; j++){
                size_t c = (scaleCycles-2) * i +j-2;
                //toDo: octaveMap is shared, need synchronization
                //if(j==1)
                //  octaveMap = Mat::zeros(blurs[scaleCyclesLevel+1].rows, blurs[scaleCyclesLevel+1].cols, CV_8UC1);
                float curSigma = par.sigmas[j];
                // find keypoints in this part of octave for curLevel
                findLevelKeypoints(curSigma, pixelDistances[i], localWrappers[c]);
                localfindAffineShapeArgs.insert(localfindAffineShapeArgs.end(), localWrappers[c].findAffineShapeArgs.begin(), localWrappers[c].findAffineShapeArgs.end());
            }
    
        #pragma omp critical
        {
            findAffineShapeArgs.insert(findAffineShapeArgs.end(), localfindAffineShapeArgs.begin(), localfindAffineShapeArgs.end());
        }
    
        #pragma omp barrier
        std::vector<Result> localRes;
        #pragma omp for schedule(dynamic) nowait
        for(int i=0; i<findAffineShapeArgs.size(); i++){
            hessianKeypointCallback->onHessianKeypointDetected(findAffineShapeArgs[i], localRes);
        }
    
        #pragma omp critical
        {
            for(size_t i=0; i<localRes.size(); i++)
                descriptors.push_back(localRes[i].descriptor);
        }
    
    }
    
    Mat gaussianBlur(const Mat input, const float sigma)
    {
       Mat ret(input.rows, input.cols, input.type());
       int size = (int)(2.0 * 3.0 * sigma + 1.0); if (size % 2 == 0) size++;      
       GaussianBlur(input, ret, Size(size, size), sigma, sigma, BORDER_REPLICATE);
       return ret;
    }
    

1 个答案:

答案 0 :(得分:2)

如果您考虑50毫秒(眨眼间的一小部分)一次成本巨额开销,那么您应该专注于您的工作流程因此。尝试以持久的方式使用一个完全初始化的进程(使用它的线程和数据结构)来增加每次运行期间完成的工作。

也就是说,可以减少开销,但无论如何,您将非常依赖库的运行时和初始化成本,从而限制了性能的可移植性。

您的绩效分析也可能存在问题。 AFAIK VTune使用采样,您的数据表示1 ms的采样间隔。这意味着在应用程序的关键初始化路径中,您可能只有50个样本,对于自信分析来说太少了。 VTune也可能具有某些形式的OpenMP工具,可在较小的时间尺度上提供更准确的结果。在任何情况下,除非我确切知道测量的影响和方法,否则我会在150毫秒内进行任何性能测量。

P.S。运行一个简单的代码,如:

#include <stdio.h>
#include <omp.h>

int main() {
  double start = omp_get_wtime();
#pragma omp parallel
  {
#pragma omp barrier
#pragma omp master
    printf("%f s\n", omp_get_wtime() - start);
  }
}

使用Intel OpenMP运行时在不同的系统/线程计数上显示3 ms到200 ms之间的初始线程创建开销。