我应该如何解释这些VTune结果?

我正在尝试使用OpenMP来并行处理此代码。 OpenCV(使用IPP构建以获得最佳效率)用作外部库。

我遇到了parallel for CPU不平衡问题,但似乎没有负载不平衡。 正如您将看到的,这可能是因为KMP_BLOCKTIME=0 ,但由于外部库(IPP,TBB,OpenMP,OpenCV),这可能是必需的。 在其余的问题中,您可以找到更多可以下载的详细信息和数据。

这些是我的VTune结果的Google Drive链接:

c755823基本KMP_BLOCKTIME = 0 30次运行:环境变量KMP_BLOCKTIME在同一输入的30次运行中设置为0的基本热点

c755823 basic 30次运行:与上面相同,但默认KMP_BLOCKTIME = 200

c755823高级KMP_BLOCKTIME = 0 30次运行:与第一次相同,但为高级热点

对于那些有兴趣的人,我可以以某种方式向您发送原始代码。

在我的英特尔i7-4700MQ上,平均10次运行的应用程序的实际挂钟时间大约为0.73秒。 我使用以下编译器标志使用icpc 2017更新3编译代码:

INTEL_OPT=-O3 -ipo -simd -xCORE-AVX2 -parallel -qopenmp -fargument-noalias -ansi-alias -no-prec-div -fp-model fast=2 -fma -align -finline-functions    
INTEL_PROFILE=-g -qopt-report=5 -Bdynamic -shared-intel -debug inline-debug-info -qopenmp-link dynamic -parallel-source-info=2 -ldl

另外,我设置KMP_BLOCKTIME=0因为默认值(200)产生了巨大的开销。

我们可以将代码分成3个并行区域(仅包含一个#pragma parallel以提高效率)和之前的一个序列,这是算法的25%左右(并且不能并行化)。

我会试着描述它们(或者你可以直接跳到代码结构):

  • 我们创建一个parallel区域以避免创建新并行区域的开销。 最终结果是填充矩阵对象的行, cv::Mat descriptor 。 我们已经3共享std::vector对象:(a)中blurs这是模糊(未并行),使用的链GuassianBlur通过OpenCV中(它使用IPP实现高斯模糊的)(b)中hessResps (尺寸已知的,说32)( c) findAffineShapeArgs (未知尺寸,但按数千个元素的顺序,比如2.3k)(d) cv::Mat descriptors (未知尺寸,最终结果)。 在序列部分,我们填充`模糊,这是一个只读向量。
  • 在第一个并行区域, hessResps使用blurs填充,没有任何同步机制。
  • 在第二个并行区域中,使用hessRespsfindLevelKeypoints填充为只读。 由于findAffineShapeArgs大小是未知的,我们需要一个本地向量localfindAffineShapeArgs ,它将在下一步中追加到findAffineShapeArgs
  • 由于findAffineShapeArgs是共享的,它的大小是未知的,所以我们需要一个critical部分,其中每个localfindAffineShapeArgs被附加到它。
  • 在第三个并行区域中,每个findAffineShapeArgs用于生成最终cv::Mat descriptor 。 同样,由于descriptors是共享的,我们需要一个本地版本cv::Mat localDescriptors
  • 最后的critical部分push_back每个localDescriptors descriptors 。 注意这是非常快的,因为cv::Mat是一个智能指针的“有点儿”,所以我们push_back指针。
  • 这是代码结构:

    cv::Mat descriptors;
    std::vector<Mat> blurs(blursSize);
    std::vector<Mat> hessResps(32);
    std::vector<FindAffineShapeArgs> findAffineShapeArgs;//we don't know its tsize in advance
    
    #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++)
        {
           hessResps[/**/] = hessianResponse(/*...*/);
        }
    
    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++){
        findLevelKeypoints(localfindAffineShapeArgs, hessResps[/*...*], /*...*/); //populate localfindAffineShapeArgs with push_back
    }
    
    #pragma omp critical{
        findAffineShapeArgs.insert(findAffineShapeArgs.end(), localfindAffineShapeArgs.begin(), localfindAffineShapeArgs.end());
    }
    
    #pragma omp barrier
    #pragma omp for schedule(dynamic) nowait
    for(int i=0; i<findAffineShapeArgs.size(); i++){
    {
      findAffineShape(findAffineShapeArgs[i]);
    }
    
    #pragma omp critical{
      for(size_t i=0; i<localRes.size(); i++)
        descriptors.push_back(localRes[i].descriptor);
    }
    }
    

    在问题的最后,你可以找到FindAffineShapeArgs

    我正在使用英特尔放大器来查看热点并评估我的应用程序。

    OpenMP潜在收益分析表明,如果存在完美的负载平衡,潜在收益将为5.8%,所以我们可以说不同CPU之间的工作负载是平衡的。

    这是我OpenMP区域的CPU使用情况直方图(请记住,这是连续运行10次的结果):

    在这里输入图像描述

    正如你所看到的,平均CPU使用率是7核心,这是很好的。

    这个OpenMP区域持续时间直方图显示在这10次运行中,平行区域总是以相同的时间执行(传播大约4毫秒):

    在这里输入图像描述

    这是来电/ Calee选项卡:

    在这里输入图像描述

    为了你的知识:

  • 在最后的平行区域中调用interpolate
  • l9_ownFilter*函数全部在最后一个并行区域中调用
  • samplePatch在最后一个并行区域被调用。
  • 在第二个并行区域调用hessianResponse
  • 现在,我的第一个问题是:我应该如何解释上述数据? 正如你所看到的,在一半的时间里,“有效使用时间”是很“正常”的功能,这可能会在核心数量变多的情况下变得“差”(例如在KNL机器上,我将测试应用程序)。

    最后,这是等待和锁定分析结果:

    在这里输入图像描述

    现在,这是第一个奇怪的事情:第276行加入屏障(对应于最昂贵的等待对象) is #pragma omp parallel,因此是并行区域的开始。 所以似乎有人在之前产生了线程。 我错了吗? 另外,等待时间比程序本身要长(我要说的是加入屏障的0.827s和1.253s)! 但也许这是指等待所有线程(而不是挂钟时间,这显然是不可能的,因为它比程序本身更长)。

    然后,第312行的显式屏障是上述代码的#pragma omp barrier ,其持续时间为0.183s。

    查看呼叫者/被叫者标签:

    在这里输入图像描述

    正如你所看到的,大部分等待时间很差,所以它指的是一个线程。 但我确定我理解这一点。 我的第二个问题是:我们能否将此解释为“所有线程都只是等待一个滞留的线程?”。

    FindAffineShapeArgs定义:

    struct FindAffineShapeArgs
    {
        FindAffineShapeArgs(float x, float y, float s, float pixelDistance, float type, float response, const Wrapper &wrapper) :
            x(x), y(y), s(s), pixelDistance(pixelDistance), type(type), response(response), wrapper(std::cref(wrapper)) {}
    
        float x, y, s;
        float pixelDistance, type, response;
        std::reference_wrapper<Wrapper const> wrapper;
    };
    

    摘要视图中潜在增益的前5个并行区域仅显示一个区域(唯一的区域)

    在这里输入图像描述

    看看“/ OpenMP区域/ OpenMP障碍对障碍”分组,这是最昂贵的循环顺序:

  • 第3圈:

    时间表(动态)nowait的pragma omp

    for(int i = 0; i

    是最昂贵的一个(正如我已经知道的),下面是扩展视图的截图:

  • 在这里输入图像描述

    正如您所看到的,许多函数来自OpenCV,它们利用IPP并且(应该)已经被优化。 扩展另外两个函数(interpolate和samplePatch)会显示[No call stack information]。 所有其他职能也一样(在其他地区也是如此)。

    第二个最昂贵的地区是第二个平行地区:

    #pragma omp for collapse(2) schedule(dynamic) nowait
    for(int i=0; i<levels; i++)
        for (int j = 2; j < scaleCycles; j++){
        findLevelKeypoints(localfindAffineShapeArgs, hessResps[/*...*], /*...*/); //populate localfindAffineShapeArgs with push_back
    }
    

    以下是展开的视图:

    在这里输入图像描述

    最后,第三个最昂贵的是第一个循环:

    #pragma omp for collapse(2) schedule(dynamic)
    for(int i=0; i<levels; i++)
        for (int j = 1; j <= scaleCycles; j++)
        {
           hessResps[/**/] = hessianResponse(/*...*/);
        }
    

    这是扩展视图:

    在这里输入图像描述

    如果您想了解更多信息,请使用我附加的VTune文件或只是询问!


    尝试阅读此链接中的信息,特别是关于“嵌套OpenMP”的部分,因为英特尔IPP已在其实施中使用OpenMP。 根据我对英特尔IPP和OpenMP的经验,如果您正在进行其他类型的多线程,并且每个创建的线程都获得OpenMP调用,那么性能非常糟糕。 此外,您可以尝试让#pragma omp parallel for为每个并行区域,而不是#pragma omp,并且除去外部#pragma omp parallel

    链接地址: http://www.djcxy.com/p/43905.html

    上一篇: How should I interpreter these VTune results?

    下一篇: malloc implementation. Where is the pointer to the next chunk?