我应该如何解释这些 VTune 结果?
Posted
技术标签:
【中文标题】我应该如何解释这些 VTune 结果?【英文标题】:How should I interpreter these VTune results? 【发布时间】:2017-05-08 09:40:53 【问题描述】:我正在尝试使用 OpenMP 并行化 this 代码。 OpenCV(使用 IPP 构建以获得最佳效率)用作外部库。
我在parallel for
s 中遇到了 CPU 使用不平衡的问题,但似乎没有负载不平衡。正如您将看到的,这可能是因为KMP_BLOCKTIME=0
,但由于外部库(IPP、TBB、OpenMP、OpenCV),这可能是必要的。在其余的问题中,您将找到更多可以下载的详细信息和数据。
这些是指向我的 VTune 结果的 Google Drive 链接:
c755823 basic KMP_BLOCKTIME=0 30 runs : 环境变量 KMP_BLOCKTIME 在相同输入的 30 次运行中设置为 0 的基本热点
c755823 basic 30 runs : 同上,但默认 KMP_BLOCKTIME=200
c755823 advanced KMP_BLOCKTIME=0 30 runs : 同第一个,但是高级热点
有兴趣的可以把原代码发给你。
在我的 Intel i7-4700MQ 上,应用程序运行 10 次的实际挂钟时间平均约为 0.73 秒。我使用 icpc
2017 update 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
这是一个模糊链(不可并行化),使用 OpenCV 的 GuassianBlur
(它使用高斯模糊的 IPP 实现)(b)hessResps
(大小已知,比如 32) (c) findAffineShapeArgs
(未知大小,但按数千个元素的顺序,比如 2.3k)(d)cv::Mat descriptors
(未知大小,最终结果)。在串行部分,我们填充 `blurs,这是一个只读向量。
在第一个并行区域中,hessResps
使用 blurs
填充,没有任何同步机制。
在第二个并行区域中,findLevelKeypoints
使用 hessResps
以只读方式填充。由于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
。
我正在使用 Intel Amplifier 查看热点并评估我的应用程序。
OpenMP 潜在增益分析表明,如果存在完美的负载平衡,潜在增益将为 5.8%,因此我们可以说工作负载在不同 CPU 之间是平衡的。
这是 OpenMP 区域的 CPU 使用率直方图(请记住,这是连续运行 10 次的结果):
如您所见,平均 CPU 使用率为 7 核,这很好。
此 OpenMP 区域持续时间直方图显示,在这 10 次运行中,并行区域始终以相同的时间执行(分布在 4 毫秒左右):
这是呼叫者/被呼叫者标签:
供您参考:
interpolate
在最后一个并行区域被调用
l9_ownFilter*
函数都在最后一个并行区域调用
samplePatch
在最后一个并行区域中被调用。
在第二个并行区域调用hessianResponse
现在,我的第一个问题是:我应该如何解释上面的数据?正如你所看到的,在许多函数中,“Effective Time by Utilization”有一半时间是“ok”的,如果内核更多,它可能会变得“Poor”(例如在 KNL 机器上,我将在其中测试下一个申请)。
最后,这是Wait and Lock分析结果:
现在,这是第一个奇怪的东西:line276
Join Barrier(对应最昂贵的等待对象) is
#pragma omp parallel`,所以是parallel region的开始。所以看来有人催生了线程之前。我错了吗?另外,等待时间比程序本身长(0.827s vs 1.253s 的 Join Barrier,我正在谈论)!但也许这是指所有线程的等待(而不是挂钟时间,这显然是不可能的,因为它比程序本身还要长)。
那么,第 312 行的 Explicit Barrier 就是上面代码的#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 Region/OpenMP Barrier-to-Barrier”分组,这是最昂贵的循环的顺序:
第三个循环:
pragma omp for schedule(dynamic) nowait
for(int i=0; 我
是最昂贵的(据我所知),这是扩展视图的屏幕截图:
如您所见,许多功能来自 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 文件或直接询问!
【问题讨论】:
你应该尽量在开始的时候总结问题和你的问题,然后最终提供所有的数据。像现在这样,读起来太长了 感谢@Antonio 的评论,我更新了这个问题并做了一个小介绍,请查看。 很抱歉,我对 x86 硬件优化和那些诊断工具都没有经验,我刚收到通知要看看这个。在我的 ARM 机器上,我避免混合使用openmp
和 tbb
。 opencv
函数与 tbb
完美平衡运行,而在 opencv
之外我使用 openmp
。我无法深入研究您的项目来判断这是否是问题所在。您是否尝试将其分解以尽可能降低复杂性?
【参考方案1】:
尝试阅读link 中的信息,尤其是关于“嵌套 OpenMP”的部分,因为英特尔 IPP 已经在其实施中使用了 OpenMP。根据我使用英特尔 IPP 和 OpenMP 的经验,如果您正在执行某种其他类型的多线程,并且当每个创建的线程到达 OpenMP 调用时,性能真的很差。此外,您可以尝试为每个并行区域设置 #pragma omp parallel for 而不是 #pragma omp for 并摆脱外部 #pragma omp parallel
【讨论】:
以上是关于我应该如何解释这些 VTune 结果?的主要内容,如果未能解决你的问题,请参考以下文章
我应该如何解释 Apache 的 ab 基准测试工具的结果?