19

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]

但我只是好奇,我为什么要在脚上开枪并使用第一组的对齐内存指令呢?

4

2 回答 2

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

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

于 2018-09-03T17:13:48.637 回答
11

_mm_loadu_ps我认为使用甚至在“Intel Nehalem 及更高版本(包括 Silvermont 及更高版本)和 AMD Bulldozer 及更高版本”之间存在细微差别,_mm_load_ps这可能会对性能产生影响。

将加载和其他操作(例如乘法)折叠到一条指令中的操作只能使用 而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;
}

这将转换为

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 编码加载,那么 unaligned 也只有两条指令

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

movaps因此,尽管在使用指令和movupsIntel Nehalem 及更高版本或 Silvermont 及更高版本或 AMD Bulldozer 及更高版本时性能没有差异,但对齐访问。

但是在没有启用 AVX 的情况下编译时使用和内在函数时的性能可能会有所不同,在编译器的权衡不是vs.的情况下,它介于负载之间或将负载折叠到 ALU 指令中。(当向量仅用作一件事的输入时会发生这种情况,否则编译器将使用负载将结果放入寄存器以供重用。)_mm_loadu_ps_mm_load_ps movapsmovupsmovupsmov*

于 2018-09-18T07:25:04.047 回答