【发布时间】:2015-05-08 22:23:08
【问题描述】:
我想知道在 Core 2 和 Westmere 之间的英特尔处理器上,如下所示的一系列指令的最佳顺序是什么。这是 AT&T 语法,因此 pxor 指令是内存读取,movdqa 是内存写入:
movdqa %xmm0, -128+64(%rbx)
movdqa %xmm1, -128+80(%rbx)
movdqa %xmm2, -128+96(%rbx)
movdqa %xmm3, -128+112(%rbx)
pxor -128(%rsp), %xmm0
pxor -112(%rsp), %xmm1
pxor -96(%rsp), %xmm2
pxor -80(%rsp), %xmm3
movdqa %xmm8, 64(%rbx)
movdqa %xmm9, 80(%rbx)
movdqa %xmm10, 96(%rbx)
movdqa %xmm11, 112(%rbx)
pxor -128(%r14), %xmm8
pxor -112(%r14), %xmm9
pxor -96(%r14), %xmm10
pxor -80(%r14), %xmm11
movdqa %xmm12, 64(%rdx)
movdqa %xmm13, 80(%rdx)
movdqa %xmm14, 96(%rdx)
movdqa %xmm15, 112(%rdx)
pxor 0(%r14), %xmm12
pxor 16(%r14), %xmm13
pxor 32(%r14), %xmm14
pxor 48(%r14), %xmm15
%r14、%rsp、%rdx 和 %rbx 是 256 的不同倍数。换句话说,上面的指令中没有不明显的别名,并且已经为对齐访问布置了数据大数据块。所有被访问的内存行都在 L1 缓存中。
一方面,我对 Agner Fog 的 optimization guides 的理解让我相信,通过如下顺序的顺序,可以按循环接近两条指令:
movdqa %xmm0, -128+64(%rbx)
movdqa %xmm1, -128+80(%rbx)
pxor -128(%rsp), %xmm0
movdqa %xmm2, -128+96(%rbx)
pxor -112(%rsp), %xmm1
movdqa %xmm3, -128+112(%rbx)
pxor -96(%rsp), %xmm2
movdqa %xmm8, 64(%rbx)
pxor -80(%rsp), %xmm3
movdqa %xmm9, 80(%rbx)
pxor -128(%r14), %xmm8
movdqa %xmm10, 96(%rbx)
pxor -112(%r14), %xmm9
movdqa %xmm11, 112(%rbx)
pxor -96(%r14), %xmm10
movdqa %xmm12, 64(%rdx)
pxor -80(%r14), %xmm11
movdqa %xmm13, 80(%rdx)
pxor 0(%r14), %xmm12
movdqa %xmm14, 96(%rdx)
pxor 16(%r14), %xmm13
movdqa %xmm15, 112(%rdx)
pxor 32(%r14), %xmm14
pxor 48(%r14), %xmm15
此排序尝试通过在读取和写入之间留下偏移量来考虑 Agner Fog 的 microachitecture.pdf 中所述的“缓存库冲突”。
另一方面,另一个担忧是,尽管程序员知道上面的代码中没有别名,但他们无法将这些信息传达给处理器。读取和写入的交错是否会引入延迟,因为处理器必须考虑读取的值可能被上述指令中的写入修改?在这种情况下,显然最好先执行所有读取操作,但由于对于特定的指令序列来说这是不可能的,所以首先完成所有写入操作可能是有意义的。
简而言之,这里似乎有很多可能性,我的直觉还不够好,无法大致了解每种可能性会发生什么。
编辑:如果这很重要,在考虑的序列之前的代码要么从内存加载xmm 寄存器,要么用算术指令计算它们,之后的代码使用这些寄存器将它们写入内存或作为算术指令的输入。已写入的内存位置不会立即重用。 rbx、rsp、r14 和 rdx 是必须来自寄存器文件的长寿命寄存器。
【问题讨论】:
-
这就是为什么使用内在函数编码 SIMD 通常比编写原始 asm 更好的几个原因之一 - 让编译器为你做指令调度等 - 这样你甚至可以重新编译不同的架构并为每个架构获取最佳代码。
-
@PaulR 我已经阅读了足够多的非 SIMD 编译器生成的汇编代码,确信在看起来合理的选择中选择最差的仍然可能比编译器生成的要好。
-
也许,但 SIMD 是一个相当不同的场景 - 每个 infrinsic 通常映射到单个指令,因此代码生成器只需要处理指令调度、寄存器分配、窥视孔优化等 - 通常大多数编译器在这方面做得很好,所以对于任何不平凡的事情,我发现 Intrinsics 会产生非常紧凑的代码。当然是 YMMV。
-
@PaulR 对于我正在考虑的那种代码,您有两个选择:处理几个小数组,这有助于编译器使用别名,但意味着它要么耗尽其寄存器,要么生成 9 字节指令大偏移量,这意味着两个永远不会在一个周期内执行。或者使用一个大数组,这意味着编译器对别名无能为力,必须使用源代码的顺序。
-
@PaulR 我正在查看的代码是 github.com/pooler/cpuminer/blob/master/scrypt-x64.S 中的
scrypt_core_3way已经有些收紧的版本。如果您认为编译器可以做得更好,请说明如何。许多人会感兴趣。
标签: optimization assembly x86-64 sse2