10

编辑 3:图像是全尺寸版本的链接。对不起,文字图片,但图表很难复制/粘贴到文本表中。


对于使用以下代码编译的程序,我有以下 VTune 配置文件icc --std=c++14 -qopenmp -axS -O3 -fPIC

VTune 配置文件

在该配置文件中,装配视图中突出显示了两组指令。尽管指令相同且顺序相同,但上层集群花费的时间明显少于下层集群。两个集群都位于同一个函数内,并且显然都被称为n时间。每次我在我现在使用的 Westmere Xeon 和 Haswell 笔记本电脑上运行分析器时都会发生这种情况(使用 SSE 编译,因为这是我现在的目标和学习内容)。

我错过了什么?

忽略较差的并发性,这很可能是由于笔记本电脑节流,因为它不会发生在桌面 Xeon 机器上。

我相信这不是微优化的例子,因为这三个加起来占总时间的相当大的百分比,我对这种行为的可能原因非常感兴趣。

编辑: OMP_NUM_THREADS=1 taskset -c 1 /opt/intel/vtune...

VTune 配置文件

相同的配置文件,尽管这次的 CPI 略低。

4

2 回答 2

3

好吧,分析汇编代码请注意,运行时间归因于下一条指令——因此,您通过指令查看的数据需要仔细解释。VTune 发行说明中有相应的说明:

运行时间归因于下一条指令 (200108041)

为了收集有关目标的耗时运行区域的数据,英特尔® VTune™ 放大器会中断正在执行的目标线程并将时间归因于上下文 IP 地址。

由于收集机制,捕获的 IP 地址指向实际消耗大部分时间的指令之后的指令。这导致运行时间被归因于汇编视图中的下一条指令(或很少归因于后续指令之一)。在极少数情况下,这也可能导致源中运行时间的错误归因 - 时间可能被错误地归因于实际热线之后的源行。

如果内联模式打开并且程序在热点内联了小函数,这可能会导致运行时间归因于错误的函数,因为下一条指令可能属于紧密内联代码中的不同函数。

于 2016-10-07T11:23:50.310 回答
3

硬件性能计数器通常将停顿计入必须等待其输入的指令,而不是缓慢产生输出的指令。

您的第一组的输入来自您的聚集。这可能会丢失很多缓存,并且这些费用不会计入那些 SUBPS/MULPS/ADDPS 指令。它们的输入直接来自 的向量负载voxel[],因此存储转发失败会导致一些延迟。但这只有大约 10 个周期 IIRC,与收集期间的缓存未命中相比很小。(这些缓存未命中在您突出显示的第一组之前显示为指令的大条)

第二组的输入直接来自缓存中可能丢失的负载。在第一组中,缓存未命中负载的直接消费者是用于设置的行的指令voxel[0],它有一个非常大的条。

但是在第二组中,缓存未命中的时间a_transfer[]归因于您突出显示的组。或者,如果不是缓存未命中,则可能是地址计算缓慢,因为负载必须等待 RAX 准备好。


看起来你可以在这里优化很多东西。

  • 而不是 store/reload for ,只需在变量a_pointf中的循环迭代中保持热状态。__m128仅当您发现编译器在溢出哪个向量寄存器(如果寄存器用完时)选择错误时,在 C 源代码中存储/重新加载才有意义。

  • 计算vi_mm_cvttps_epi32(vf)因此 ROUNDPS 不是收集索引的依赖链的一部分。

  • 通过将voxel窄负载改组到向量中来收集自己,而不是编写复制到数组然后从中加载的代码。(保证存储转发失败,请参阅Agner Fog 的优化指南和来自标签 wiki的其他链接)。

    对地址数学进行部分向量化可能是值得的(计算base_0,使用PMULDQ 和常数向量),因此您只需一个或两个 MOVQ(约 1 或 2 个周期延迟)而不是存储/重新加载(约 5 个周期延迟)在哈斯韦尔,我忘了。)

    使用 MOVD 加载两个相邻short的值,并使用 PINSRD 将另一对合并到第二个元素中。您可能会从中获得好的代码_mm_setr_epi32(*(const int*)base_0, *(const int*)(base_0 + dim_x), 0, 0),除了指针别名是未定义的行为。您可能会从_mm_setr_epi16(*base_0, *(base_0 + 1), *(base_0 + dim_x), *(base_0 + dim_x + 1), 0,0,0,0).

    然后用 PMOVSX 将低 4 个 16 位元素扩展为 32 位元素整数,并用(CVTDQ2PS)float将它们全部转换为并行。_mm_cvtepi32_ps

  • 您的标量 LERP 没有被自动矢量化,但是您正在并行执行两个(并且可能会保存一条指令,因为您无论如何都希望将结果保存在矢量中)。

  • 调用floorf()是愚蠢的,函数调用会强制编译器将所有 xmm 寄存器溢出到内存中。使用或其他方式编译以-ffast-math使其内联到 ROUNDSS,或手动执行。特别是因为您继续将您计算的浮点数加载到向量中!

  • 使用向量比较而不是标量 prev_x / prev_y / prev_z。使用 MOVMASKPS 将结果转换为可以测试的整数。(您只关心低 3 个元素,因此使用compare_mask & 0b0111(true 如果设置了 4 位掩码的任何低 3 位,在比较不等于 之后,请使用_mm_cmpneq_ps. 请参阅double指令版本以获取更多表关于它是如何工作的:http ://www.felixcloutier.com/x86/CMPPD.html )。

于 2016-10-07T21:56:10.730 回答