SIMD 优于 Cray 样式向量的问题有哪些?

Are there any problems for which SIMD outperforms Cray-style vectors?

CPUs 旨在提供高性能的数字运算,最终得到某种向量指令集。基本上有两种:

  1. SIMD。这在概念上很简单,例如您拥有第二组 128 位寄存器,而不是仅仅拥有一组 64 位寄存器和对其进行操作,并且您可以同时对两个 64 位值的短向量进行操作。它在实现中变得复杂,因为您还希望可以选择对四个 32 位值进行操作,然后新的 CPU 生成提供 256 位向量,这需要一整套新的指令等。

  2. 较旧的 Cray 样式向量指令,其中向量开始时​​很大,例如4096 位,但同时操作的元素数量是透明的,你想在给定操作中使用的元素数量是一个指令参数。我们的想法是,您预先减少一点复杂性,以避免以后逐渐增加的复杂性。

有人认为选项 2 更好,并且这些论点似乎有道理,例如https://www.sigarch.org/simd-instructions-considered-harmful/

至少乍一看,选项 2 似乎可以做选项 1 可以做的所有事情,而且更容易,而且总体上更好。

是否存在相反情况的工作负载? SIMD 指令在哪些方面可以完成 Cray 风格的向量不能做的事情,或者可以用更少的代码更快地完成某些事情?

Cray-style 向量非常适合 pure-vertical 问题,有些人认为 SIMD 仅限于解决这类问题。它们使您的代码与未来具有更宽向量的 CPU 向前兼容。

我从未使用过 Cray-style 矢量,所以我不知道让它们进行水平随机播放的范围有多大。

如果您不特别限制 Cray,现代 instruction-sets 像 ARM SVE 和 RISC-V extension V 也为您提供 forward-compatible 具有可变矢量宽度的代码,并且明确设计为避免 short-fixed-vector SIMD ISA(如 AVX2 和 AVX-512,以及 ARM NEON)的问题。

我认为他们有一些洗牌能力。绝对是掩蔽,但我对他们还不够熟悉,不知道他们是否可以做像 left-pack () or prefix-sum (parallel prefix (cumulative) sum with SSE).

这样的事情

然后会出现这样的问题,即您一次处理少量固定数量的数据,但超出了整数寄存器的容量。例如 尽管在一些初始广播之后基本上仍然对每个元素做同样的事情。


但是 等 one-off 自定义洗牌和水平成对乘法可以通过一些 single-uop 指令完成正确工作的其他东西需要两者之间的紧密集成SIMD 和内核的整数部分(如在 x86 CPUs 上)以获得最佳性能。将 SIMD 部分用于其擅长的部分,然后将向量的低两个 32 位元素放入整数寄存器以完成其余工作。 Cray 模型的一部分是(我认为)与 CPU 管道的松散耦合;那会像那样打败 use-cases。尽管某些带有 NEON 的 32 位 ARM CPUs 具有相同的松散耦合,其中从向量到整数的移动很慢。

一般的文本解析和 atoi 是 use-case 具有混洗功能的短向量有效的地方。例如https://www.phoronix.com/scan.php?page=article&item=simdjson-avx-512&num=1 - 25% to 40% speedup from AVX-512 with simdjson 2.0 for parsing JSON, over the already-fast performance of AVX2 SIMD. (See 关于在 2016 年 JSON 使用 SIMD 的问答。

其中许多技巧依赖于特定于 x86 的 pmovmskb eax, xmm0 来获取矢量比较结果的整数位图。例如,您可以测试它是全零还是全 1 (cmp eax, 0xffff) 以留在 memcmpmemchr 循环的主循环中。如果不是,则 bsf eax,eax 找到第一个差异的位置,可能在 not.

之后

将矢量宽度限制为可以放入整数寄存器的元素数量是实现这一点的关键,尽管您可以想象 instruction-set 和 compare-into-mask 具有可缩放宽度的掩码寄存器。 (也许 ARM SVE 已经是那样了?我不确定。)

“传统”矢量方法(Cray、CDC/ETA、NEC 等)出现在 t运行 晶体管预算有限且可商用 low-latency SRAM 主存储器。在这种技术体制下,处理器没有 t运行 电阻预算来实现 out-of-order 操作的完整记分板和互锁,而目前可用于允许 multi-cycle floating-point 的流水线操作操作。相反,创建了一个矢量指令集。向量算术指令保证运行向量中的连续操作是独立的并且可以流水线化。扩展硬件以允许多个向量并行操作相对容易,因为依赖性检查只需要“每个向量”而不是“每个元素”完成。

Cray ISA RISC-like 将数据从内存加载到向量寄存器,执行算术 register-to-register,然后将结果从向量寄存器存储回内存。最大向量长度最初是 64 个元素,后来是 128 个元素。

CDC/ETA 系统使用“memory-to-memory”架构,其中算术指令指定所有输入和输出的内存位置,以及 1 到 65535 个元素的向量长度。

None 的“传统”向量机使用数据缓存进行向量运算,因此性能受到从内存加载数据的速率的限制。 SRAM 主存储器占系统成本的主要部分。在 1990 年代初期,SRAM cost/bit 仅为 DRAM 的 2 倍左右,但 DRAM 价格下降如此之快,以至于到 2002 年 SRAM price/MiB 是 DRAM 的 75 倍——甚至不再遥不可及。

传统机器的 SRAM 内存是 word-addressable(64 位字)并且存储量非常大以允许几乎全速进行线性、跨步(只要避免 2 的幂)和运行dom 访问。这导致了一种广泛使用 non-unit-stride 内存访问模式的编程风格。这些访问模式会导致缓存机器出现性能问题,并且随着时间的推移,使用缓存系统的开发人员不再使用它们——因此代码不太能够利用矢量系统的这种能力。 随着代码被 re-written 用于使用缓存系统,人们慢慢发现缓存对于向量机上 运行 的大多数应用程序来说工作得很好。 Re-use 的缓存数据减少了所需的内存带宽量,因此在 microprocessor-based 系统上的应用程序 运行 比从主内存带宽比率预期的要好得多。

到 1990 年代后期,传统向量机的市场几乎消失,工作负载 t运行 主要分配给使用 RISC 处理器和 multi-level 缓存层次结构的 shared-memory 机器。开发了一些 government-subsidized 矢量系统(尤其是在日本),但这些系统对高性能计算的影响很小,对一般计算的影响 none。

故事还没有结束——经过许多 not-very-successful 尝试(由几家供应商)让向量和缓存很好地协同工作,NEC 开发了一个非常有趣的系统(NEC SX-Aurora Tsubasa ) 将多核向量寄存器处理器设计与 DRAM (HBM) 主存储器和有效的共享高速缓存相结合。我特别喜欢使用单个执行线程生成超过 300 GB/s 内存带宽的能力——这是 AMD 或 Intel 处理器单线程可用带宽的 10-25 倍。

所以答案是,即使在 SIMD 被包含在内之前,带有缓存内存的微处理器的低成本就已经将向量机赶出了市场。 SIMD 对于某些专门的操作具有明显的优势,并且随着时间的推移变得更加通用——尽管随着 SIMD 宽度的增加优势逐渐减弱。矢量方法在架构意义上并没有死(例如,NEC 矢量引擎),但它的优势通常被认为被软件与主流架构模型不兼容的缺点所淹没。