-3

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

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

在此处输入图像描述

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

如您所见,这些函数是、_clonestart_thread它们看起来像 OpenMP 内部或系统调用,但没有指定从何处调用这些函数。_kmp_launch_thread_kmp_fork_barrier

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

在此处输入图像描述

但是,我从不打电话initInterTab2d,也不知道我正在使用的一些实验室(尤其是 OpenCV)是否调用了它。

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

在此处输入图像描述

并解释函数/调用堆栈选项卡:

在此处输入图像描述

但同样,我不能真正理解为什么这些函数,为什么它们需要这么长时间以及为什么只有主线程在它们期间工作,而其他线程处于“障碍”状态。

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

请注意,我只有一个#pragma omp parallel区域,即此图像的选定部分(在右侧):

在此处输入图像描述

代码结构如下:

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

这是代码的主要功能:

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;
}
4

1 回答 1

2

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

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

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

PS运行一个简单的代码,如:

#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);
  }
}

显示使用英特尔 OpenMP 运行时在不同系统/线程数上 3 毫秒到 200 毫秒之间的初始线程创建开销。

于 2017-04-30T10:06:59.773 回答