在对齐和未对齐的x86 SIMD指令之间进行选择

时间:2018-09-03 09:57:11

标签: x86 sse simd avx avx512

通常有两种类型的SIMD指令:

A。使用对齐的内存地址的地址,如果地址未在操作数大小边界上对齐,则会引发一般保护(#GP)异常:

movaps  xmm0, xmmword ptr [rax]
vmovaps ymm0, ymmword ptr [rax]
vmovaps zmm0, zmmword ptr [rax]

B。而那些使用未对齐内存地址的地址,则不会引发此类异常:

movups  xmm0, xmmword ptr [rax]
vmovups ymm0, ymmword ptr [rax]
vmovups zmm0, zmmword ptr [rax]

但是我很好奇,为什么我要朝自己的脚开枪并完全使用第一组中对齐的内存指令?

2 个答案:

答案 0 :(得分:11)

  • 未对齐的访问:只能使用movups/vmovups。统一访问案例中讨论的相同处罚(见下)也适用于此。此外,跨越缓存行或虚拟页面边界的访问总是会在所有处理器上造成损失。
  • 对齐访问:
    • 在Intel Nehalem和更高版本(包括Silvermont和更高版本)以及AMD Bulldozer和更高版本上:预解码后,它们以相同的精确方式对相同的操作数执行。这包括对消除运动的支持。对于提取和预解码阶段,它们为相同的操作数消耗相同的资源。
    • 在Nehalem和Bonnell之前以及Bull-dozer之前:它们被解码为不同的融合域uops和未融合域uops。 movups/vmovups在管道的前端和后端消耗更多的资源(最多两倍)。换句话说,就延迟和/或吞吐量而言,movups/vmovups的速度可能是movaps/vmovaps的两倍。

因此,如果您不关心较旧的微体系结构,则两者在技术上是等效的。尽管如果您知道或期望数据会对齐,则应使用对齐的指令来确保数据确实对齐,而不必在代码中添加显式检查。

答案 1 :(得分:7)

我认为即使在“ Intel Nehalem和更高版本(包括Silvermont和更高版本)以及AMD Bulldozer和更高版本”上使用_mm_loadu_ps_mm_load_ps之间也存在细微的差异,这可能会对性能产生影响。

除非使用启用了允许未对齐内存操作数的AVX进行编译,否则只能使用load而不是loadu内部函数来完成将负载和另一操作(例如乘法)折叠的操作。

考虑以下代码

#include <x86intrin.h>
__m128 foo(float *x, float *y) {
    __m128 vx = _mm_loadu_ps(x);
    __m128 vy = _mm_loadu_ps(y);
    return vx*vy;
}

这得到converted to

movups  xmm0, XMMWORD PTR [rdi]
movups  xmm1, XMMWORD PTR [rsi]
mulps   xmm0, xmm1

但是,如果使用对齐的载荷固有函数(_mm_load_ps),则会将其编译为

movaps  xmm0, XMMWORD PTR [rdi]
mulps   xmm0, XMMWORD PTR [rsi]

保存一条指令。但是,如果编译器可以使用VEX编码的负载,则为only two instructions for unaligned as well

vmovups xmm0, XMMWORD PTR [rsi]
vmulps  xmm0, xmm0, XMMWORD PTR [rdi]

因此,尽管在Intel Nehalem和更高版本,Silvermont和更高版本,AMD Bulldozer和更高版本上使用movapsmovups指令时,性能没有差异,但仍可以进行对齐访问。

但是在未启用AVX的情况下进行编译时,在使用_mm_loadu_ps_mm_load_ps intrinsics 时,在性能上可能会有所不同。权衡不是movapsmovups的权衡,而是在movups或将负载折叠到ALU指令之间。 (当向量仅用作一件事的输入时,会发生这种情况,否则编译器将使用mov*加载将结果存入寄存器以供重用。)