Advanced Vector Extensions(AVX)是针对Intel和AMD微处理器的x86指令集架构的扩展。
使用int8 AVX512-VNNI指令的有效方法,尤其是将数据加载到zmm寄存器
我想在 int8 数据中使用 AVX512-VNNI 指令优化矩阵乘法运算。 我了解 vpdpbusd 的工作原理,但我不知道如何有效地使用它。 详细一点,我
我实现了int8矩阵乘法的8位整数乘法。 这是我的代码,但我认为它真的很慢。 内联 __m512i int8_mul(__m512i a, __m512i b) { // 将向量 INT8 转换为 I...
他们是: #定义多数(a, b, c) _mm256_xor_si256(_mm256_xor_si256(_mm256_and_si256(a, b), _mm256_and_si256(b, c)), _mm256_and_si256(a, c)) #define 选择(e, f, g) _mm256_xor_si256(
SIMD shuffle 可以将哪些执行端口用于 AVX2 和 NEON?
在查看英特尔优化参考手册时,我注意到以下部分:处理端口 5 压力。 它基本上表示 Sandy Bridge 微架构中的端口 5 包括随机播放单元,这些单元的频率...
SIMD 指令可以用于 AVX2 和 NEON 的哪些执行端口?
在查看英特尔优化参考手册时,我注意到以下部分:处理端口 5 压力。 它基本上表示 Sandy Bridge 微架构中的端口 5 包括随机播放单元,这些单元的频率...
在 x86-64 SIMD 指令名称以及可用于从 C/C++ 访问它们的内在函数中,您会发现术语 shuffle(例如 _mm_shuffle_epi32)和 permute(例如 _mm_permute_...
用最少的指令加载并复制 4 个单精度浮点数到打包的 __m256 变量中
我有一个包含 A、B、C、D 4 个浮点数字的浮点数组,我希望将它们加载到 __m256 变量中,例如 ABBCCDD。最好的方法是什么? 我知道使用 _mm256_set_ps() 始终是一个选择...
我需要从每 24 位输入中解包两个 16 位值。 (3 字节 -> 4 字节)。我已经以幼稚的方式做到了,但我对表现不满意。 例如,InBuffer 是 __m128i: 价值...
当用 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 )。 MSVC 始终允许所有别名,并且它并不总是在 GCC 或 Clang 上中断,因此有些人错误地认为像您这样的代码是安全的。 事实并非如此,正如本例所示。 不幸的是 -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)); } 将 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; 这可行,但通常会编译成缓慢的代码,矢量存储然后标量加载。 要计算向量中所有数字的水平总和,请参阅答案。
我有一个32位整数数组,我想将其转换为双精度数,并希望使用_mm256_cvtepi32_pd()来执行转换。 我的问题是这个内在函数仅转换 8 个整数中的 4 个...
从不同的 goroutine 同时调用时,AVX512 程序集会中断
我有一个自定义的 golang (1.23.0) 程序集,它执行 AVX512 操作以加速非常常见的代码路径。该函数检查一组玩家是否持有扑克手
shuffling 的 api 仅支持 byte 和 sbyte // // 概括: // __m256i _mm256_shuffle_epi8 (__m256i a, __m256i b) // // VPSHUFB ymm, ymm, ymm/m256 // // 参数: ...
AVX(2)/SIMD 方式获取/设置(至 1)256 位寄存器中的单个位
当前但hacky的方法是这样的: __m256i 位集(__m256i 源,uint8_t 索引){ uint8_t pos_in_64 = 索引 % 64; uint8_t位置=索引/64; uint64_t 位掩码 = 1ULL << pos_i...
这是代码: #包括 #包括 #包括 #包括 __m256i foo(); __m256i foo2(); int main() { __m256i vec1 = foo();
我想在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 代码: #包括 佛罗里达...
模拟 AVX512 VPCOMPRESSB 字节打包,无需 AVX512_VBMI2
我已经用 0-63 的字节整数数组填充了 zmm 寄存器。 这些数字充当矩阵的索引。 非零元素表示矩阵中包含数据的行。 并非所有行
模拟 AVX512 VPCOMPESSB 字节打包,无需 AVX512_VBMI2
我已经用 0-63 的字节整数数组填充了 zmm 寄存器。 这些数字充当矩阵的索引。 非零元素表示矩阵中包含数据的行。 并非所有行