单指令,多数据(SIMD)是使每个指令在小块或数据元素矢量上操作的概念。 CPU矢量指令集包括:x86 SSE和AVX,ARM NEON和PowerPC AltiVec。为了有效地使用SIMD指令,数据需要采用数组结构形式,并且应该在更长的流中发生。天真的“SIMD优化”代码通常比原始代码运行速度慢。
在 x86-64 SIMD 指令名称以及可用于从 C/C++ 访问它们的内在函数中,您会发现术语 shuffle(例如 _mm_shuffle_epi32)和 permute(例如 _mm_permute_...
当用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是一个 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #包括 用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #include <stdio.h> #include <x86intrin.h> int sum_of_squares(int x[], int n) { int sum = 0; __m256i sum8 = _mm256_set1_epi32(0); for (int i = 0; i < n; i += 8) { __m256i x8 = _mm256_load_si256((__m256i *)&x[i]); x8 = _mm256_mul_epi32(x8, x8); sum8 = _mm256_add_epi32(x8, sum8); } int *_sum = (int *)&sum8; for (int i = 0; i < 8; i++) sum += _sum[i]; return sum; } int main() { _Alignas(32) int x[16]; for (int i = 0; i < 15; i++) { x[i] = i; } printf("%d", sum_of_squares(x, 16)); } 将 int* 指向 __m256i(long long 元素的 GNU C 向量)违反了严格别名规则。 因此,GCC14.2 -O2 将您的函数优化为 return 0;。 (xor eax,eax / ret). 我们可以通过使用 -fno-strict-aliasing 并看到非零结果来证明这是问题所在。 (在 Godbolt 上查看两者)不幸的是 -fsanitize=undefined 没有捕获此错误(或 x[] 的未初始化的最终元素)。 但一般来说,当您检查 asm 并看到您的函数优化为 return 0; 时,这通常意味着您要么返回了错误的变量,要么存在编译时可见的 UB。 (有时编译器甚至不设置返回值,甚至省略 ret 指令,因此执行会陷入接下来的情况。) 使用 _mm256_storeu_si256 到 int tmparr[8];(或者对齐 tmp 数组并使用 _mm256_store_si256)。 或者更好,请参阅 使用 AVX512 或 AVX2 计算所有打包 32 位整数之和的最快方法(一般来说 进行水平 SSE 向量和(或其他缩减)的最快方法) - 洗牌将低半部分向下与低半部分对齐并进行垂直 SIMD 加法,每一步减少一半的元素数量,直到你只剩下一个了。 (如果您避免使用 UB,GCC 实际上会将您的归约循环优化为一系列洗牌和添加。) 测试用例中其他不相关的错误 正如 Soonts 提到的,您还需要 vpmulld (_mm256_mullo_epi32) 非展宽 32 位乘法,而不是 vpmuldq (_mm256_mul_epi32) 展宽有符号乘法,后者仅读取偶数元素以产生 64 位结果。 你的严格别名错误就是你得到零的原因;解决这个问题对于获得您想要的非零结果也是必要的,而不仅仅是偶数元素(0,2等)的总和。 您还应该初始化第 16 个元素 (x[15]),正如 Soonts 指出的那样,您当前的代码无法做到这一点。 __attribute__((may_alias)) 让您可以键入 int 的版本,您可以指向任何东西 如果你真的想要,你可以使用 typedef int32_t aliasing_i32 __attribute__((may_alias)),你可以安全地指向任何对齐 4 或更多的东西,使用它代替 int 作为你的指针类型。 (或者如果您使用 __attribute__((may_alias, aligned(1))),则不对齐)。 有趣的事实:GCC 和 Clang 对 __m256i 的定义使用 may_alias,允许您将 __m256i* 指向任何东西,但反之则不然。 但这并没有什么好处;数组不占用“额外”存储空间; __m256i向量优化为仅YMM寄存器。 无论如何,归约循环都会优化为随机播放,但如果没有,无论哪种编写方式都会有 32 字节的堆栈空间,该空间由 vmovdqa 写入并由标量循环读取。 x8 = _mm256_mul_epi32(x8, x8); 指导并没有达到你期望的效果。它仅使用每个 64 位通道中的最低 32 位,并计算 4 个乘积,每个乘积 64 位。 要计算 8 个 32 位乘积,请考虑使用 _mm256_mullo_epi32。 for (int i = 0; i < 15; i++) 请注意,您仅初始化 15 个数字,然后加载 16 个。 int *_sum = (int *)&sum8; 这可行,但通常会编译成缓慢的代码,矢量存储然后标量加载。 要计算向量中所有数字的水平总和,请参阅答案。 #include <stdio.h> #include <x86intrin.h> int sum_of_squares(int x[], int n) { if( 0 != (n+1)%8 ) { // Here n needs to be multiples of 8 - 1 printf( "sum_of_squares data size parameter needs to be multiples of 8-1\n"); printf( "data size n: %d n-1: %d\n", n, n-1); printf( "Allowed values for n: 7, 15, 23, ...\n"); return -1; } int sum = 0; __m256i sum8 = _mm256_set1_epi32(0); for (int i = 0; i < n; i += 8) { __m256i initVal = _mm256_load_si256((__m256i *)&x[i]); __m256i x8 = _mm256_load_si256((__m256i *)&x[i]); // x8 = _mm256_mul_epi32(x8, x8); x8 = _mm256_mullo_epi32( initVal, x8); sum8 = _mm256_add_epi32(x8, sum8); } int *_sum = (int *)&sum8; for (int i = 0; i < 8; i++) sum += _sum[i]; return sum; } int main() { { _Alignas(32) int x[8]; for (int i = 0; i < 8; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..7^2 elements: %d\n", sum_of_squares(x, 8-1)); int n = 8-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[16]; for (int i = 0; i < 16; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..15^2 elements: %d\n", sum_of_squares(x, 16-1)); int n = 16-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[24]; for (int i = 0; i < 24; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 24-1)); int n = 24-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[4]; for (int i = 0; i < 4; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 4-1)); int n = 4-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[13]; for (int i = 0; i < 13; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 13-1)); int n = 13-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } // I feel better to use mathematical formula instead of wasting memories at RAM and process handlers. return 0; } /* $ /usr/bin/gcc.exe -mavx2 -g -Wall sum_of_squares.c -o ./a.out $ ./a.out sum_of_squares of 0^2+1^2+2^2+..7^2 elements: 140 7*(7+1)*(2*7+1)/6 = 7*8*(14+1)/6 = 7*8*15/6 = 140 sum_of_squares of 0^2+1^2+2^2+..15^2 elements: 1240 15*(15+1)*(2*15+1)/6 = 15*16*(30+1)/6 = 15*16*31/6 = 1240 sum_of_squares of 0^2+1^2+2^2+..23^2 elements: 4324 23*(23+1)*(2*23+1)/6 = 23*24*(46+1)/6 = 23*24*47/6 = 4324 sum_of_squares data size parameter needs to be multiples of 8-1 data size n: 3 n-1: 2 Allowed values for n: 7, 15, 23, ... sum_of_squares of 0^2+1^2+2^2+..23^2 elements: -1 3*(3+1)*(2*3+1)/6 = 3*4*(6+1)/6 = 3*4*7/6 = 14 sum_of_squares data size parameter needs to be multiples of 8-1 data size n: 12 n-1: 11 Allowed values for n: 7, 15, 23, ... sum_of_squares of 0^2+1^2+2^2+..23^2 elements: -1 12*(12+1)*(2*12+1)/6 = 12*13*(24+1)/6 = 12*13*25/6 = 650 */
当用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是一个 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #包括 用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #include <stdio.h> #include <x86intrin.h> int sum_of_squares(int x[], int n) { int sum = 0; __m256i sum8 = _mm256_set1_epi32(0); for (int i = 0; i < n; i += 8) { __m256i x8 = _mm256_load_si256((__m256i *)&x[i]); x8 = _mm256_mul_epi32(x8, x8); sum8 = _mm256_add_epi32(x8, sum8); } int *_sum = (int *)&sum8; for (int i = 0; i < 8; i++) sum += _sum[i]; return sum; } int main() { _Alignas(32) int x[16]; for (int i = 0; i < 15; i++) { x[i] = i; } printf("%d", sum_of_squares(x, 16)); } x8 = _mm256_mul_epi32(x8, x8); 指导并没有达到你期望的效果。它仅使用每个 64 位通道中的最低 32 位,并计算 4 个乘积,每个乘积 64 位。 要计算 8 个 32 位乘积,请考虑使用 _mm256_mullo_epi32。 for (int i = 0; i < 15; i++) 请注意,您仅初始化 15 个数字,然后加载 16 个。 int *_sum = (int *)&sum8; 这可行,但通常会编译成缓慢的代码,矢量存储然后标量加载。 要计算向量中所有数字的水平总和,请参阅答案。
将ARM NEON中每个字节的高位打包,像AVX512 vpmovb2m一样为64字节?
__builtin_ia32_cvtb2mask512() 是 vpmovb2m k、zmm 的 GNU C 内置函数。 它的 Intel 内在函数是 _mm512_movepi8_mask。 它从每个字节中提取最高有效位,产生一个整数......
为什么对于 RGB 到灰度,SIMD 只提高一点点性能,SIMD 乘法但向量元素的标量加法?
我正在学习如何使用SIMD进行图像处理。但是,我想知道为什么使用SIMD后性能没有太大改善。 图片尺寸:3840*2160 图片格式:PixelFormat。
在 C# 中,我将 RGB 图像数据存储在 byte[] 数组 ([r, g, b, r, g, b, ...]) 中,并尝试将其转换为灰度。我正在 C#(使用指针)和...
sse4.2 _mm_cmpistrm/_mm_cmpesrm 指令得到错误结果
我想使用下面的代码来计算数组a和数组b的交集: #包括 #包括 #包括 无效测试(uint16_t *a,uint16_t *b...
AVX(2)/SIMD 方式获取/设置(至 1)256 位寄存器中的单个位
当前但hacky的方法是这样的: __m256i 位集(__m256i 源,uint8_t 索引){ uint8_t pos_in_64 = 索引 % 64; uint8_t位置=索引/64; uint64_t 位掩码 = 1ULL << pos_i...
c++ 如何编写编译器可以轻松针对 SIMD 进行优化的代码?
我正在使用 Visual Studio 2008,在项目设置中我看到“激活扩展指令集”选项,我可以将其设置为 None、SSE 或 SSE2 所以编译器会尝试批处理...
了解 Clang 的 SIMD 优化,用于将 float 乘以 int 循环计数器
给定以下函数 void foo(float* 结果, int 大小, float y, float delta) { 对于 (int t = 0; t < size; ++t) { result[t] = y + delta * t; } } Clang with -O2 generates the
GCC 可以自动“向量化循环”。 是否还有其他广泛可用的编译器可以执行相同的操作?
我想在SIMD中优化以下代码 拍:[1,2,3,4] 数据:[1,1,3,3] mask: [1, 0, 1, 1] # 1表示相等,0表示可选/不关心 结果:[1,1,1,0] 天真的算法...
gcc 11.4 openmp 似乎没有生成 AVX2 指令。根据之前的 stackoverflow,我做错了什么?
我在 popos 上使用 gcc 11.4 我尝试过的命令: g++ -fopenmp-simd -O2 -S simd_reduction.cpp g++ -fopenmp-simd -O2 -mavx2 -march=native -S simd_reduction.cpp 代码: #包括 佛罗里达...
使用 Vector API 优化 Java 中 int16 向量点积的计算
TL;DR:使用 Java 的 Vector API 优化 16 位整数数组乘法而不溢出。 我正在尝试优化应用激活函数和计算的性能关键循环...
我有一个函数,它接受两个数组并用两个数组坐标的双线性组合填充第三个数组。该函数是 8 维实向量的克利福德乘积
模拟 AVX512 VPCOMPRESSB 字节打包,无需 AVX512_VBMI2
我已经用 0-63 的字节整数数组填充了 zmm 寄存器。 这些数字充当矩阵的索引。 非零元素表示矩阵中包含数据的行。 并非所有行
模拟 AVX512 VPCOMPESSB 字节打包,无需 AVX512_VBMI2
我已经用 0-63 的字节整数数组填充了 zmm 寄存器。 这些数字充当矩阵的索引。 非零元素表示矩阵中包含数据的行。 并非所有行
我正在为我的 opengl 项目制作自己的 linalg 库,并且正在考虑使用 simd 加速 matmul。 最小可重现示例: 使用 std::arch::x86_64::*; #[导出(调试、克隆、复制)] 圣...
我正在为我的 opengl 项目制作自己的 linalg 库,并且正在考虑使用 simd 加速 matmul。 最小可重现示例: 使用 std::arch::x86_64::*; #[导出(调试、克隆、复制)] 圣...
我想将 SSE/AVX 寄存器左移或右移 32 位的倍数,同时移入零。 让我更准确地说明我感兴趣的轮班。对于 SSE,我想做以下轮班......