许多CPU具有“向量”或“SIMD”指令集,它们同时对两个、四个或更多个数据块应用相同的操作。现代x86芯片有SSE指令,许多PPC芯片有“Altivec”指令,甚至一些ARM芯片有向量指令集,称为NEON。
“矢量化”(简化)是重写循环的过程,这样它就不会处理数组的单个元素 N 次,而是同时处理(比如说)数组的 4 个元素 N/4 次。
我选择 4 是因为它是现代硬件最有可能直接支持 32 位浮点数或整数的值。
矢量化和循环展开之间的区别:考虑以下非常简单的循环,它将两个数组的元素相加并将结果存储到第三个数组。
for (int i=0; i<16; ++i)
C[i] = A[i] + B[i];
展开这个循环会将其转换为如下所示:
for (int i=0; i<16; i+=4) {
C[i] = A[i] + B[i];
C[i+1] = A[i+1] + B[i+1];
C[i+2] = A[i+2] + B[i+2];
C[i+3] = A[i+3] + B[i+3];
}
另一方面,对其进行矢量化会产生如下结果:
for (int i=0; i<16; i+=4)
addFourThingsAtOnceAndStoreResult(&C[i], &A[i], &B[i]);
其中“addFourThingsAtOnceAndStoreResult”是编译器用来指定向量指令的任何内部函数的占位符。
术语:
请注意,大多数现代的提前编译器都能够自动矢量化像这样非常简单的循环,通常可以通过编译选项启用(默认情况下在现代 C 和 C++ 编译器中进行完全优化,例如gcc -O3 -march=native
)。开放MP#pragma omp simd
有时有助于提示编译器,特别是对于“归约”循环,例如对 FP 数组求和,其中矢量化需要假装 FP 数学是关联的。
更复杂的算法仍然需要程序员的帮助来生成良好的矢量代码;我们称之为手动矢量化,通常与 x86 等内在函数一起使用_mm_add_ps
映射到单个机器指令,如下所示Intel cpu 上的 SIMD 前缀和 https://stackoverflow.com/questions/10587598/simd-prefix-sum-on-intel-cpu or 如何使用 SIMD 计算字符出现次数 https://stackoverflow.com/questions/54541129/how-to-count-character-occurrences-using-simd。或者甚至使用 SIMD 来解决简短的非循环问题,例如将 9 个字符数字转换为 int 或 unsigned int 的最疯狂的最快方法 https://stackoverflow.com/questions/70420948/most-insanely-fastest-way-to-convert-9-char-digits-into-an-int-or-unsigned-int or 如何将二进制整数转换为十六进制字符串? https://stackoverflow.com/questions/53823756/how-to-convert-a-binary-integer-number-to-a-hex-string/66518284#66518284
还使用术语“矢量化”描述更高级别的软件转换,您可以完全抽象出循环,只描述对数组的操作,而不是对组成数组的元素的操作。例如写作C = A + B
在某些语言中,当这些是数组或矩阵时,允许这样做,这与 C 或 C++ 不同。在这样的低级语言中,您可以描述调用 BLAS 或 Eigen 库函数,而不是作为矢量化编程风格手动编写循环。关于这个问题的其他一些答案集中在矢量化和高级语言的含义上。