向量化(通常使用的术语)指的是 SIMD(单指令,多数据)操作。
本质上,这意味着一条指令对多个操作数并行执行相同的操作。例如,要将大小为 N 的向量乘以标量,我们将 M 称为它可以同时操作的大小的操作数的数量。如果是这样,那么它需要执行的指令数大约是 N/M,其中(对于纯标量操作)它必须执行 N 次操作。
例如,英特尔当前的 AVX 2 指令集使用 256 位寄存器。这些可用于保存(和操作)一组 4 个 64 位操作数,或 8 个 32 位操作数。
因此,假设您正在处理 32 位单精度实数,这意味着一条指令可以一次执行 8 次操作(在您的情况下为乘法),因此(至少在理论上)您可以完成N 次乘法仅使用 N/8 乘法指令。至少,理论上,这应该使操作完成的速度大约是一次执行一条指令所允许的速度的 8 倍。
当然,确切的好处取决于每条指令支持多少操作数。 Intel 的第一次尝试只支持 64 位寄存器,因此要同时操作 8 个项目,这些项目每个只能是 8 位。他们目前支持 256 位寄存器,并且他们已经宣布支持 512 位(他们甚至可能已经在一些高端处理器中提供了这种支持,但至少目前还没有在普通消费者处理器中提供)。委婉地说,充分利用这种能力也并非易事。调度指令以使您实际上有 N 个操作数可用并在正确的时间在正确的位置(根本)不一定是一件容易的事。
从长远来看,(现在古老的)Cray 1 正是通过这种方式获得了很大的速度。它的向量单元在每个 64 位的 64 个寄存器组上运行,因此每个时钟周期可以进行 64 次双精度运算。在最佳矢量化代码上,它比您仅基于其(低得多的)时钟速度所预期的更接近当前 CPU 的速度。不过,充分利用这一点并不总是那么容易(现在仍然不是)。
但请记住,向量化不是 CPU 可以并行执行操作的唯一方式。还有指令级并行的可能性,它允许单个 CPU(或 CPU 的单个内核)一次执行多个指令。大多数现代 CPU 包括硬件(理论上)每个时钟周期执行最多约 4 条指令1,如果指令是负载、存储和 ALU 的混合。它们可以相当常规地平均每个时钟执行近 2 条指令,或者在内存不是瓶颈时在经过良好调整的循环中执行更多。
当然,还有多线程——在(至少在逻辑上)独立的处理器/内核上运行多个指令流。
因此,现代 CPU 可能有 4 个内核,每个内核每个时钟可以执行 2 个向量乘法,并且这些指令中的每一个都可以对 8 个操作数进行操作。因此,至少在理论上,每个时钟可以执行 4 * 2 * 8 = 64 次操作。
某些指令的吞吐量更好或更差。例如,FP 增加的吞吐量低于 FMA 或在 Skylake 之前在 Intel 上相乘(每个时钟 1 个向量而不是 2 个)。但是像 AND 或 XOR 这样的布尔逻辑每个时钟吞吐量有 3 个向量;构建 AND/XOR/OR 执行单元不需要很多晶体管,因此 CPU 会复制它们。使用高吞吐量指令时,总流水线宽度(解码并发送到内核无序部分的前端)的瓶颈很常见,而不是特定执行单元上的瓶颈。
- 但是,随着时间的推移,CPU 往往有更多可用资源,因此这个数字会上升。