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

在对齐与未对齐的x86 SIMD指令之间进行选择,x86,sse,simd,avx,avx512,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] vm

通常有两种类型的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]
但我只是好奇,为什么我要开枪打自己的脚,并使用第一组的对齐内存指令?

  • 未对齐访问:只能使用
    movups/vmovups
    。对齐访问案例(见下一步)中讨论的相同惩罚也适用于此处。此外,跨越缓存线或虚拟页边界的访问总是会对所有处理器造成惩罚
  • 对齐访问:
    • 在Intel Nehalem及更高版本(包括Silvermont及更高版本)和AMD推土机及更高版本上:预编码后,它们以相同的方式对相同的操作数执行。这包括对移动消除的支持。对于fetch和predecode阶段,它们为相同的操作数消耗相同的资源
    • 在pre-Nehalem和Bonnell以及pre-推土机上:它们被解码为不同的融合域UOP和未融合域UOP
      movups/vmovups
      在管道的前端和后端消耗更多的资源(最多两倍)。换句话说,
      movups/vmovups
      的延迟和/或吞吐量可能是
      movaps/vmovaps
      的两倍

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

我认为即使在“英特尔Nehalem”及更高版本上使用
\u mm\u loadu\u ps
\u mm\u load\u ps
之间也存在细微差别(包括Silvermont和更高版本)和AMD推土机和更高版本”,这可能会对性能产生影响

只有使用
load
,而不是
loadu
内部函数,才能将加载和其他操作(如乘法)折叠成一条指令的操作,除非在编译时启用AVX以允许未对齐的内存操作数

考虑以下代码

#include <x86intrin.h>
__m128 foo(float *x, float *y) {
    __m128 vx = _mm_loadu_ps(x);
    __m128 vy = _mm_loadu_ps(y);
    return vx*vy;
}
但是,如果使用对齐的加载内部函数(
\u mm\u load\u ps
),则会将其编译为

movaps  xmm0, XMMWORD PTR [rdi]
mulps   xmm0, XMMWORD PTR [rsi]
这节省了一条指令。但是如果编译器可以使用VEX编码的加载,那么它就是

因此,即使在英特尔Nehalem及更高版本、Silvermont及更高版本、AMD推土机及更高版本上使用指令
movaps
movups
时,性能没有差异,也可以进行对齐访问


但是,在未启用AVX的情况下编译时,使用
\u mm\u loadu\u ps
\u mm\u loadu\u ps
内部函数时,性能可能会有所不同,如果编译器的折衷不是
movaps
movups
,而是介于
movups
或将负载折叠到ALU指令之间。(当向量仅用作一个对象的输入时,会发生这种情况,否则编译器将使用
mov*
load在寄存器中获取结果以供重用。)

对齐与未对齐的加载是一个历史人工制品(请参阅)。今天,未对齐的加载执行相同的操作-尽管自然对齐的操作数具有从不跨越缓存线或页面的优点。@备忘录链接的答案充满了错误信息和过时的信息。未对齐的操作现在只有一些小的惩罚。不管怎样,由于Nehalem,重要的是地址对齐,而不是对齐“是的,作为一个内置的“assert-aligned”仍然可以使用。”,@harold Microsoft和Intel都将此提升到了一个新的水平。从VS2017和ICC2018开始,两个编译器都将生成未对齐的移动,即使是针对Nehalem之前的目标。MS已收到此消息,但他们不再关心,因为Nehalem之前的版本太旧了。@MikeF可能。缓存的每个存储都是原子的,但总线宽度较窄的较旧CPU将影响将SSE存储作为两个/四个独立的存储。如果第三个存储因延迟TLB失效而发生故障,则将每个存储推送到存储缓冲区,然后从存储缓冲区中独立刷新(见4.10.4.4)那么第一个可能已经被刷新到缓存中了。我相信Intel说他们可以按照重复加载/存储UOP的顺序自由地实现SIMD加载/存储。一个
前缀会解决这个问题吗?我不知道如何解决。为什么你不在这里这么正式地问一下呢?这很有趣!谢谢。不过我很好奇,这两者是否都是错误的在现代CPU上的性能几乎相同,为什么他们不在(v)中消除GP例外movaps指令?为什么不将其别名。@MikeF这些指令有不同的编码,而现有的应用程序可能需要一条或两条指令。因此,运行此类应用程序需要支持两条编码。此外,对齐版本在硬件中实现对齐检查,这可能消除在软件中执行这些检查的需要用于需要对齐数据的代码。@MikeF-因为一旦指令在ISA中以某种方式定义,您就无法通过简单的文档更新来改变其行为!异常是这种行为的一部分。OP询问的是asm指令,而不是加载内部函数。不过,还是对有用的相关点进行了投票。(AVX指令不要求它们的内存操作数对齐,但SSE需要对齐,因此在没有AVX的情况下编译
loadu
intrinsic可能会花费额外的指令,即使在现代CPU上也很重要。)@PeterCordes,我在您的评论之前意识到了我的错误,并已将其修复:-@PeterCordes是您的编辑“将一个加载和另一个操作(如乘法)折叠到一条指令中的操作只能在加载时进行,而不能在加载时进行。”准确。如果加载时遇到困难,可以对加载进行折叠
movaps  xmm0, XMMWORD PTR [rdi]
mulps   xmm0, XMMWORD PTR [rsi]
vmovups xmm0, XMMWORD PTR [rsi]
vmulps  xmm0, xmm0, XMMWORD PTR [rdi]