18

在我处理快速 ADD 循环(加速 x64 汇编器 ADD 循环)时,我正在使用 SSE 和 AVX 指令测试内存访问。要添加,我必须读取两个输入并产生一个输出。所以我写了一个虚拟例程,它将两个 x64 值读入寄存器并将一个写回内存而不做任何操作。这当然没用,我只是为了进行基准测试。

我使用一个展开的循环,每个循环处理 64 个字节。它由 8 个块组成,如下所示:

mov rax, QWORD PTR [rdx+r11*8-64]
mov r10, QWORD PTR [r8+r11*8-64]
mov QWORD PTR [rcx+r11*8-64], rax

然后我将其升级到 SSE2。现在我使用 4 个这样的块:

movdqa xmm0, XMMWORD PTR [rdx+r11*8-64]
movdqa xmm1, XMMWORD PTR [r8+r11*8-64]
movdqa XMMWORD PTR [rcx+r11*8-64], xmm0

后来我使用了 AVX(每个寄存器 256 位)。我有两个这样的块:

vmovdqa ymm0, YMMWORD PTR [rdx+r11*8-64]
vmovdqa ymm1, YMMWORD PTR [r8+r11*8-64]
vmovdqa YMMWORD PTR [rcx+r11*8-64], ymm0

到目前为止,还没有那么壮观。有趣的是基准测试结果:当我对 1k+1k=1k 64 位字(即两次 8 kb 输入和一次 8kb 输出)运行三种不同的方法时,我得到了奇怪的结果。以下每个时序用于将两次 64 字节输入处理为 64 字节输出。

  • x64 寄存器方法以大约 15 个周期/64 字节运行
  • SSE2 方法以大约 8.5 个周期/64 字节运行
  • AVX 方法以大约 9 个周期/64 字节运行

我的问题是:为什么 AVX 方法比 SSE2 方法慢(虽然不是很多)?我预计它至少会达到同等水平。使用 YMM 寄存器会花费这么多额外的时间吗?内存是对齐的(否则你会得到 GPF)。

有人对此有解释吗?

4

1 回答 1

14

在 Sandybridge/Ivybridge 上,256b AVX 加载和存储被破解为加载/存储执行单元中的两个 128b 操作 [正如 Peter Cordes 所指出的,这些不是完全微操作,但清除端口的操作需要两个周期],所以没有理由期望使用这些指令的版本会更快。

为什么它更慢?想到了两种可能:

  • 对于基址 + 索引 + 偏移寻址,128b 加载的延迟为 6 个周期,而 256b 加载的延迟为 7 个周期(英特尔优化手册中的表 2-8)。尽管您的基准测试应该受thoughput而不是latency的约束,但更长的延迟意味着处理器需要更长的时间才能从任何打嗝(管道气泡或预测未命中或中断服务或......)中恢复,这确实会产生一些影响。

  • 在同一文档的 11.6.2 中,英特尔建议 256b 加载的高速缓存行和页面交叉的惩罚可能比 128b 加载更大。如果您的加载不是全部 32 字节对齐,这也可以解释您在使用 256b 加载/存储操作时看到的减速:

例 11-12 显示了 SAXPY 的两个实现,地址未对齐。备选方案 1 使用 32 字节加载,备选方案 2 使用 16 字节加载。这些代码示例使用两个源缓冲区 src1、src2(与 32 字节对齐的 4 个字节偏移)和一个目标缓冲区 DST(即 32 字节对齐)执行。使用两个 16 字节内存操作代替 32 字节内存访问执行速度更快。

于 2012-12-25T23:53:10.107 回答