【发布时间】:2017-07-16 05:44:00
【问题描述】:
(后续代码审查question here,包含此循环上下文的更多详细信息。)
环境:
- Windows 7 x64
- VS 2017 社区
- 在 Intel i7700k (kaby Lake) 上定位 x64 代码
我不会写很多汇编代码,而且当我写的时候,它要么足够短,要么足够简单,以至于我不必担心从中挤出最大数量的性能。我的更复杂的代码通常是用 C 编写的,我让编译器的优化器担心延迟、代码对齐等问题。
但是,在我当前的项目中,MSVC 的优化器在我的关键路径中的代码上做得非常糟糕。所以...
我还没有找到一个好的工具来对 x64 汇编代码进行静态或运行时分析,以消除停顿、改善延迟等。我所拥有的只是 VS 分析器,它告诉我(大约) 哪些指令花费的时间最多。墙上的时钟告诉我最新的变化是让事情变得更好或更糟。
作为替代方案,我一直在阅读 Agner 的文档,希望能从我的代码中获得更多性能。问题是在你完全理解之前很难理解他的任何作品。但其中的一部分是有道理的,我正在尝试应用我所学到的。
请记住,这是我最内层循环的核心,这(毫不奇怪)是 VS 分析器说我的时间花在哪里:
nottop:
vpminub ymm2, ymm2, ymm3 ; reset out of range values
vpsubb ymm2, ymm2, ymm0 ; take a step
top:
vptest ymm2, ymm1 ; check for out of range values
jnz nottop
; Outer loop that does some math, does a "vpsubb ymm2, ymm2, ymm0",
; and eventually jumps back to top
是的,这几乎是一个教科书式的依赖链示例:这个紧密的小循环中的每条指令都依赖于前一个操作的结果。这意味着不可能有并行性,这意味着我没有充分利用处理器。
受 Agner 的“优化汇编器”文档的启发,我提出了一种方法,(希望)允许我一次执行 2 个操作,因此我可以让一个管道更新 ymm2 和另一个更新(比如)ymm8。
虽然这是一个不平凡的改变,所以在我开始把所有东西都拆开之前,我想知道它是否可能会有所帮助。查看 Agner 为 kaby Lake(我的目标)提供的“说明表”,我发现:
uops
each
port Latency
pminub p01 1
psubb p015 1
ptest p0 p5 3
鉴于此,看起来当一个管道使用 p0+p5 对 ymm2 执行 vptest 时,另一个管道可以使用 p1 在 ymm8 上执行 vpminub 和 vpsubb。是的,vptest 后面的事情仍然会堆积起来,但它应该会有所帮助。
或者会吗?
我目前正在从 8 个线程运行此代码(是的,8 个线程确实给了我比 4、5、6 或 7 更好的总吞吐量)。鉴于我的 i7700k 有 4 个超线程内核,每个内核上运行 2 个线程这一事实是否意味着我 已经 将端口最大化?端口是“每个核心”,而不是“每个逻辑 CPU”,对吗?
所以。
根据我目前对 Agner 工作的理解,似乎没有办法进一步优化当前形式的代码。如果我想要更好的性能,我需要想出一种不同的方法。
是的,我敢肯定,如果我在这里发布了我的整个 asm 例程,有人可能会建议另一种方法。但是这个问题的目的不是让别人为我写我的代码。我正在尝试看看我是否开始了解如何考虑优化 asm 代码。
这是(大致)看待事物的正确方式吗?我错过了几件吗?或者这完全是错误的?
【问题讨论】:
-
“我还没有找到一个好的工具来对 x64 汇编代码进行静态或运行时分析,以消除停顿、改善延迟等。”认识IACA。它支持的最新微架构是 Skylake,但据我所知,SKL 和 KBL 的区别相对较小。 (不幸的是,如果英特尔停止继续更新,该工具在未来的用处将变得不那么大。:-()
-
@CodyGray 感谢您的介绍。发布此问题后,我遇到了 iaca(请参阅下面的“部分答案”)。 “差异相对较小”的问题是我不知道这些差异可能在哪里。从“第 6 代”到“第 7 代”时,对 SSE 的更改似乎是一个合理的改进领域。我对试图定义这些差异可能存在的想法并不感到兴奋。除此之外,iaca 并没有告诉我太多我没有从 Agner 的工作中收集到的信息。它只是为我节省了查找说明以查看他们使用的端口的操作。
-
相对较小的差异可能是夸大其词。据我所知,Kaby Lake 与 Skylake 的微架构相同,并且时间相同。就像从 Haswell 到 Broadwell 或从 Broadwell 到 Skylake 跳跃一样,差别不大,但 Kaby Lake 确实是“无操作”。
-
@DavidWohlferd - 进入
nottop循环时ymm3和ymm0的典型值是什么?它们在外循环中会发生变化吗? -
@BeeOnRope - ymm0 和 ymm3 都是常量,在初始化时加载一次。也就是说,这个问题的目的是测试我对 asm 优化的理解。我已经掌握了一些基础知识,但显然我还有很长的路要走。我希望尽快在 codereview 上发布此代码的可运行版本。
标签: performance assembly x86-64 micro-optimization avx2