aarch64 上未对齐 SIMD 加载/存储的性能

Posted

技术标签:

【中文标题】aarch64 上未对齐 SIMD 加载/存储的性能【英文标题】:Performance of unaligned SIMD load/store on aarch64 【发布时间】:2018-01-24 15:42:16 【问题描述】:

older answer 表示 aarch64 支持未对齐的读/写,并提到了性能成本,但不清楚答案是否也仅涵盖 ALU 或 SIMD(128 位寄存器)操作。

相对于对齐的 128 位 NEON 加载和存储,在 aarch64 上未对齐的 128 位 NEON 加载和存储要慢多少(如果有的话)?

对于未对齐的 SIMD 加载和存储是否有单独的指令(如 SSE2 的情况),或者已知对齐的加载/存储是否与潜在未对齐的加载/存储相同的指令?

【问题讨论】:

我没有 arm64 但如果有的话很容易测试。尝试阅读this并自己提供答案 使用 Rust 进行测试,似乎至少在 Raspberry Pi 3 中,来自对齐地址的潜在未对齐读取(LLVM 内置 memcpy)与对齐读取(LLVM 指针 deref)一样快来自对齐的地址。 从 LLVM 查看程序集,看起来对齐和未对齐的负载是相同的指令。 GCC 怎么样? 我的输入是 Rust 并且 rustc 使用 LLVM 而不是 GCC 作为 codegen 后端。 【参考方案1】:

根据 4.6 加载/存储对齐部分中的Cortex-A57 Software Optimization Guide,它说:

ARMv8-A 架构允许任意对齐多种类型的加载和存储访问。 Cortex-A57 处理器可以处理大多数未对齐的访问而不会造成性能损失。但是,也有一些情况 减少带宽或产生额外的延迟,如下所述:

加载跨越缓存线(64 字节)边界的操作 存储跨越 16 字节边界的操作

因此,它可能取决于您使用的处理器,无序(A57、A72、A-72、A-75)或无序(A-35、A-53、A-55)。我没有找到任何有序处理器的优化指南,但是它们确实有一个硬件性能计数器,您可以使用它来检查未对齐指令的数量是否会影响性能:

    0xOF_UNALIGNED_LDST_RETIRED Unaligned load-store

这可以与perf 工具一起使用。

AArch64 中没有针对非对齐访问的特殊说明。

【讨论】:

【参考方案2】:

如果加载/存储必须拆分或跨越缓存行,则至少需要一个额外的周期。

有详尽的表格指定了Cortex-A8(按顺序)和Cortex-A9(部分OoO)的各种对齐所需的周期数和寄存器数。例如,带有一个 reg 的 vld1 对于未对齐访问与 64 位对齐访问相比,有 1 个周期的惩罚。

Cortex-A55(按顺序)最多可进行 64 位加载和 128 位存储,因此,its optimization manual 的第 3.3 节指出以下情况会产生 1 个周期的惩罚:

• 加载跨越 64 位边界的操作 • 跨越 128 位边界的 128 位存储操作

根据its optimization guide 的第 5.4 节,Cortex-A75 (OoO) 有以下处罚:

• 加载跨越 64 位边界的操作。 • 在 AArch64 中,所有跨越 128 位边界的存储。 • 在 AArch32 中,所有跨越 64 位边界的存储。

正如吉列尔莫的回答一样,A57 (OoO) 会受到以下处罚:

• 跨越缓存线(64 字节)边界的加载操作 • 跨 [128 位] 边界的存储操作

考虑到 A55 和 A75 跨越 64 位边界,我有点怀疑 A57 不会受到惩罚。所有这些都有 64 字节的缓存线;他们也应该因跨越缓存线而受到处罚。最后,注意有unpredictable behavior for split access crossing pages。

从使用 Cavium ThunderX 的一些粗略测试(没有性能计数器)来看,似乎更接近 2 个周期的惩罚,但这可能是在循环中具有背靠背未对齐加载和存储的附加效应.


AArch64 NEON 指令不区分对齐和未对齐(例如,参见 LD1)。对于 AArch32 NEON,对齐在寻址中静态指定 (VLDn):

vld1.32 d16-d17, [r0]    ; no alignment
vld1.32 d16-d17, [r0@64] ; 64-bit aligned
vld1.32 d16-d17, [r0:64] ; 64 bit-aligned, used by GAS to avoid comment ambiguity

我不知道在最近以 AArch32 模式运行的芯片上,没有对齐限定符的对齐访问是否比使用对齐限定符的访问慢。 ARM 的一些旧文档鼓励尽可能使用限定符。 (相比之下,英特尔改进了他们的芯片,使未对齐和对齐的移动在地址对齐时执行相同的操作。)

如果您使用内在函数,MSVC 具有接受对齐的_ex-后缀变体。让 GCC 发出对齐限定符的可靠方法是使用 __builtin_assume_aligned

// MSVC
vld1q_u16_ex(addr, 64);
// GCC:
addr = (uint16_t*)__builtin_assume_aligned(addr, 8);
vld1q_u16(addr);

【讨论】:

【参考方案3】:

在 aarch64 上不使用对齐提示。它们是透明的。如果指针与数据类型大小对齐,则性能优势是自动的。

如果有疑问,对于 GCC/Clang,在变量声明中使用 __attribute__((__aligned__(16)))

【讨论】:

以上是关于aarch64 上未对齐 SIMD 加载/存储的性能的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL纹理在四边形上未对齐

使用 SIMD 指令避免无效的内存加载

ARMV8 datasheet学习笔记4:AArch64系统级体系结构之系统级存储模型

ARMv8-A非对齐数据访问支持(Alignment support)

x86 上未对齐的指针

PPL Combinable 的 SIMD 对齐问题