我应该如何解释这些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
填充,没有任何同步机制。 hessResps
将findLevelKeypoints
填充为只读。 由于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?