什么可能导致相同的SSE代码在同一个函数中运行速度慢几倍?

iks*_*nov 9 c++ optimization sse intel intel-vtune

编辑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略低.

Pet*_*des 3

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

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

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

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


看起来这里有很多可以优化的地方

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

  • vi使用进行计算_mm_cvttps_epi32(vf),因此 ROUNDPS 不是收集索引的依赖链的一部分。

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

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

    使用 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在与 不等于 进行比较后,如果设置了 4 位掩码的任何一个低 3 位,则使用 (true 进行测试_mm_cmpneq_psdouble有关更多表格,请参阅指令版本关于它是如何工作的:http://www.felixcloutier.com/x86/CMPPD.html)。